diff --git a/sai.txt b/sai.txt new file mode 100644 index 000000000..985e34c21 --- /dev/null +++ b/sai.txt @@ -0,0 +1 @@ +hsiai diff --git a/src/osx/.gitignore b/src/osx/.gitignore deleted file mode 100644 index e5e41a58c..000000000 --- a/src/osx/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## Build generated -build/ -DerivedData/ - -## Various settings -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata/ - -## Other -*.moved-aside -*.xccheckout -*.xcscmblueprint - -## Obj-C/Swift specific -*.hmap -*.ipa -*.dSYM.zip -*.dSYM - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output - -# Code Injection -# -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ diff --git a/src/osx/Directory.Build.props b/src/osx/Directory.Build.props deleted file mode 100644 index 46656d77e..000000000 --- a/src/osx/Directory.Build.props +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - $(RepoOutPath)osx\ - $(PlatformOutPath)$(MSBuildProjectName)\ - $(ProjectOutPath)bin\ - $(ProjectOutPath)obj\ - - diff --git a/src/osx/Installer.Mac/Installer.Mac.csproj b/src/osx/Installer.Mac/Installer.Mac.csproj deleted file mode 100644 index daabd20d4..000000000 --- a/src/osx/Installer.Mac/Installer.Mac.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - net8.0 - false - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/osx/Installer.Mac/build.sh b/src/osx/Installer.Mac/build.sh deleted file mode 100755 index ce877e6b7..000000000 --- a/src/osx/Installer.Mac/build.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash -die () { - echo "$*" >&2 - exit 1 -} - -echo "Building Installer.Mac..." - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -INSTALLER_SRC="$SRC/osx/Installer.Mac" -INSTALLER_OUT="$OUT/osx/Installer.Mac" - -# Parse script arguments -for i in "$@" -do -case "$i" in - --configuration=*) - CONFIGURATION="${i#*=}" - shift # past argument=value - ;; - --runtime=*) - RUNTIME="${i#*=}" - shift - ;; - --version=*) - VERSION="${i#*=}" - shift # past argument=value - ;; - *) - # unknown option - ;; -esac -done - -# Perform pre-execution checks -CONFIGURATION="${CONFIGURATION:=Debug}" -if [ -z "$VERSION" ]; then - die "--version was not set" -fi - -if [ -z "$RUNTIME" ]; then - TEST_RUNTIME=`uname -m` - case $TEST_RUNTIME in - "x86_64") - RUNTIME="osx-x64" - ;; - "arm64") - RUNTIME="osx-arm64" - ;; - *) - die "Unknown runtime '$TEST_RUNTIME'" - ;; - esac -fi - -OUTDIR="$INSTALLER_OUT/pkg/$CONFIGURATION" -PAYLOAD="$OUTDIR/payload" -COMPONENTDIR="$OUTDIR/components" -COMPONENTOUT="$COMPONENTDIR/com.microsoft.gitcredentialmanager.component.pkg" -DISTOUT="$OUTDIR/gcm-$RUNTIME-$VERSION.pkg" - -# Layout and pack -"$INSTALLER_SRC/layout.sh" --configuration="$CONFIGURATION" --output="$PAYLOAD" --runtime="$RUNTIME" || exit 1 -"$INSTALLER_SRC/pack.sh" --payload="$PAYLOAD" --version="$VERSION" --output="$COMPONENTOUT" || exit 1 -"$INSTALLER_SRC/dist.sh" --package-path="$COMPONENTDIR" --version="$VERSION" --output="$DISTOUT" --runtime="$RUNTIME" || exit 1 - -echo "Build of Installer.Mac complete." diff --git a/src/osx/Installer.Mac/codesign.sh b/src/osx/Installer.Mac/codesign.sh deleted file mode 100755 index d66c8acd9..000000000 --- a/src/osx/Installer.Mac/codesign.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -SIGN_DIR=$1 -DEVELOPER_ID=$2 -ENTITLEMENTS_FILE=$3 - -if [ -z "$SIGN_DIR" ]; then - echo "error: missing directory argument" - exit 1 -elif [ -z "$DEVELOPER_ID" ]; then - echo "error: missing developer id argument" - exit 1 -elif [ -z "$ENTITLEMENTS_FILE" ]; then - echo "error: missing entitlements file argument" - exit 1 -fi - -echo "======== INPUTS ========" -echo "Directory: $SIGN_DIR" -echo "Developer ID: $DEVELOPER_ID" -echo "Entitlements: $ENTITLEMENTS_FILE" -echo "======== END INPUTS ========" - -cd $SIGN_DIR -for f in * -do - macho=$(file --mime $f | grep mach) - # Runtime sign dylibs and Mach-O binaries - if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; - then - echo "Runtime Signing $f" - codesign -s "$DEVELOPER_ID" $f --timestamp --force --options=runtime --entitlements $ENTITLEMENTS_FILE - elif [ -d "$f" ]; - then - echo "Signing files in subdirectory $f" - cd $f - for i in * - do - codesign -s "$DEVELOPER_ID" $i --timestamp --force - done - cd .. - else - echo "Signing $f" - codesign -s "$DEVELOPER_ID" $f --timestamp --force - fi -done diff --git a/src/osx/Installer.Mac/dist.sh b/src/osx/Installer.Mac/dist.sh deleted file mode 100755 index f26761e26..000000000 --- a/src/osx/Installer.Mac/dist.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash -die () { - echo "$*" >&2 - exit 1 -} - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -INSTALLER_SRC="$SRC/osx/Installer.Mac" -RESXPATH="$INSTALLER_SRC/resources" - -# Product information -IDENTIFIER="com.microsoft.gitcredentialmanager.dist" - -# Parse script arguments -for i in "$@" -do -case "$i" in - --version=*) - VERSION="${i#*=}" - shift # past argument=value - ;; - --package-path=*) - PACKAGEPATH="${i#*=}" - shift # past argument=value - ;; - --output=*) - DISTOUT="${i#*=}" - shift # past argument=value - ;; - --runtime=*) - RUNTIME="${i#*=}" - shift - ;; - --identity=*) - IDENTITY="${i#*=}" - shift - ;; - *) - # unknown option - ;; -esac -done - -# Perform pre-execution checks -if [ -z "$VERSION" ]; then - die "--version was not set" -fi -if [ -z "$PACKAGEPATH" ]; then - die "--package-path was not set" -elif [ ! -d "$PACKAGEPATH" ]; then - die "Could not find '$PACKAGEPATH'. Did you run pack.sh first?" -fi -if [ -z "$DISTOUT" ]; then - die "--output was not set" -fi -if [ -z "$RUNTIME" ]; then - TEST_RUNTIME=`uname -m` - case $TEST_RUNTIME in - "x86_64") - RUNTIME="osx-x64" - ;; - "arm64") - RUNTIME="osx-arm64" - ;; - *) - die "Unknown runtime '$TEST_RUNTIME'" - ;; - esac -fi - -echo "Building for runtime '$RUNTIME'" - -if [ "$RUNTIME" == "osx-x64" ]; then - DISTPATH="$INSTALLER_SRC/distribution.x64.xml" -else - DISTPATH="$INSTALLER_SRC/distribution.arm64.xml" -fi - -# Cleanup any old package -if [ -e "$DISTOUT" ]; then - echo "Deleteing old product package '$DISTOUT'..." - rm "$DISTOUT" -fi - -# Ensure the parent directory for the package exists -mkdir -p "$(dirname "$DISTOUT")" - -# Build product installer -echo "Building product package..." -/usr/bin/productbuild \ - --package-path "$PACKAGEPATH" \ - --resources "$RESXPATH" \ - --distribution "$DISTPATH" \ - --identifier "$IDENTIFIER" \ - --version "$VERSION" \ - ${IDENTITY:+"--sign"} ${IDENTITY:+"$IDENTITY"} \ - "$DISTOUT" || exit 1 - -echo "Product build complete." diff --git a/src/osx/Installer.Mac/distribution.arm64.xml b/src/osx/Installer.Mac/distribution.arm64.xml deleted file mode 100644 index 531bdbe33..000000000 --- a/src/osx/Installer.Mac/distribution.arm64.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - Git Credential Manager - - - - - - - - - - - - - - - - com.microsoft.gitcredentialmanager.component.pkg - - diff --git a/src/osx/Installer.Mac/distribution.x64.xml b/src/osx/Installer.Mac/distribution.x64.xml deleted file mode 100644 index 45deec220..000000000 --- a/src/osx/Installer.Mac/distribution.x64.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - Git Credential Manager - - - - - - - - - - - - - - - - com.microsoft.gitcredentialmanager.component.pkg - - diff --git a/src/osx/Installer.Mac/entitlements.xml b/src/osx/Installer.Mac/entitlements.xml deleted file mode 100644 index 9acbcfa5c..000000000 --- a/src/osx/Installer.Mac/entitlements.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-library-validation - - - \ No newline at end of file diff --git a/src/osx/Installer.Mac/layout.sh b/src/osx/Installer.Mac/layout.sh deleted file mode 100755 index ad8e2cfc2..000000000 --- a/src/osx/Installer.Mac/layout.sh +++ /dev/null @@ -1,111 +0,0 @@ -#!/bin/bash -die () { - echo "$*" >&2 - exit 1 -} -make_absolute () { - case "$1" in - /*) - echo "$1" - ;; - *) - echo "$PWD/$1" - ;; - esac -} - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -INSTALLER_SRC="$SRC/osx/Installer.Mac" -GCM_SRC="$SRC/shared/Git-Credential-Manager" -GCM_UI_SRC="$SRC/shared/Git-Credential-Manager.UI.Avalonia" - -# Build parameters -FRAMEWORK=net8.0 - -# Parse script arguments -for i in "$@" -do -case "$i" in - --configuration=*) - CONFIGURATION="${i#*=}" - shift # past argument=value - ;; - --output=*) - PAYLOAD="${i#*=}" - shift # past argument=value - ;; - --runtime=*) - RUNTIME="${i#*=}" - shift # past argument=value - ;; - --symbol-output=*) - SYMBOLOUT="${i#*=}" - ;; - *) - # unknown option - ;; -esac -done - -# Determine a runtime if one was not provided -if [ -z "$RUNTIME" ]; then - TEST_RUNTIME=`uname -m` - case $TEST_RUNTIME in - "x86_64") - RUNTIME="osx-x64" - ;; - "arm64") - RUNTIME="osx-arm64" - ;; - *) - die "Unknown runtime '$TEST_RUNTIME'" - ;; - esac -fi - -echo "Building for runtime '$RUNTIME'" - -# Perform pre-execution checks -CONFIGURATION="${CONFIGURATION:=Debug}" -if [ -z "$PAYLOAD" ]; then - die "--output was not set" -fi -if [ -z "$SYMBOLOUT" ]; then - SYMBOLOUT="$PAYLOAD.sym" -fi - -# Cleanup any old payload directory -if [ -d "$PAYLOAD" ]; then - echo "Cleaning old payload directory '$PAYLOAD'..." - rm -rf "$PAYLOAD" -fi - -# Ensure payload and symbol directories exists -mkdir -p "$PAYLOAD" "$SYMBOLOUT" - -# Copy uninstaller script -echo "Copying uninstall script..." -cp "$INSTALLER_SRC/uninstall.sh" "$PAYLOAD" || exit 1 - -# Publish core application executables -echo "Publishing core application..." -dotnet publish "$GCM_SRC" \ - --configuration="$CONFIGURATION" \ - --framework="$FRAMEWORK" \ - --runtime="$RUNTIME" \ - --self-contained \ - --output="$(make_absolute "$PAYLOAD")" || exit 1 - -# Collect symbols -echo "Collecting managed symbols..." -mv "$PAYLOAD"/*.pdb "$SYMBOLOUT" || exit 1 - -# Remove any unwanted .DS_Store files -echo "Removing unnecessary files..." -find "$PAYLOAD" -name '*.DS_Store' -type f -delete || exit 1 - -echo "Layout complete." diff --git a/src/osx/Installer.Mac/notarize.sh b/src/osx/Installer.Mac/notarize.sh deleted file mode 100755 index 9315d688a..000000000 --- a/src/osx/Installer.Mac/notarize.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -for i in "$@" -do -case "$i" in - --package=*) - PACKAGE="${i#*=}" - shift # past argument=value - ;; - --keychain-profile=*) - KEYCHAIN_PROFILE="${i#*=}" - shift # past argument=value - ;; - *) - die "unknown option '$i'" - ;; -esac -done - -if [ -z "$PACKAGE" ]; then - echo "error: missing package argument" - exit 1 -elif [ -z "$KEYCHAIN_PROFILE" ]; then - echo "error: missing keychain profile argument" - exit 1 -fi - -# Exit as soon as any line fails -set -e - -# Send the notarization request -xcrun notarytool submit -v "$PACKAGE" -p "$KEYCHAIN_PROFILE" --wait - -# Staple the notarization ticket (to allow offline installation) -xcrun stapler staple -v "$PACKAGE" diff --git a/src/osx/Installer.Mac/pack.sh b/src/osx/Installer.Mac/pack.sh deleted file mode 100755 index b58f4ce5a..000000000 --- a/src/osx/Installer.Mac/pack.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -die () { - echo "$*" >&2 - exit 1 -} - -# Directories -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -ROOT="$( cd "$THISDIR"/../../.. ; pwd -P )" -SRC="$ROOT/src" -OUT="$ROOT/out" -INSTALLER_SRC="$SRC/osx/Installer.Mac" - -# Product information -IDENTIFIER="com.microsoft.gitcredentialmanager" -INSTALL_LOCATION="/usr/local/share/gcm-core" - -# Parse script arguments -for i in "$@" -do -case "$i" in - --version=*) - VERSION="${i#*=}" - shift # past argument=value - ;; - --payload=*) - PAYLOAD="${i#*=}" - shift # past argument=value - ;; - --output=*) - PKGOUT="${i#*=}" - shift # past argument=value - ;; - *) - # unknown option - ;; -esac -done - -# Perform pre-execution checks -if [ -z "$VERSION" ]; then - die "--version was not set" -fi -if [ -z "$PAYLOAD" ]; then - die "--payload was not set" -elif [ ! -d "$PAYLOAD" ]; then - die "Could not find '$PAYLOAD'. Did you run layout.sh first?" -fi -if [ -z "$PKGOUT" ]; then - die "--output was not set" -fi - -# Cleanup any old component -if [ -e "$PKGOUT" ]; then - echo "Deleteing old component '$PKGOUT'..." - rm "$PKGOUT" -fi - -# Ensure the parent directory for the component exists -mkdir -p "$(dirname "$PKGOUT")" - -# Set full read, write, execute permissions for owner and just read and execute permissions for group and other -echo "Setting file permissions..." -/bin/chmod -R 755 "$PAYLOAD" || exit 1 - -# Remove any extended attributes (ACEs) -echo "Removing extended attributes..." -/usr/bin/xattr -rc "$PAYLOAD" || exit 1 - -# Build component packages -echo "Building core component package..." -/usr/bin/pkgbuild \ - --root "$PAYLOAD/" \ - --install-location "$INSTALL_LOCATION" \ - --scripts "$INSTALLER_SRC/scripts" \ - --identifier "$IDENTIFIER" \ - --version "$VERSION" \ - "$PKGOUT" || exit 1 - -echo "Component pack complete." diff --git a/src/osx/Installer.Mac/resources/background.png b/src/osx/Installer.Mac/resources/background.png deleted file mode 100644 index fb9c1b154..000000000 Binary files a/src/osx/Installer.Mac/resources/background.png and /dev/null differ diff --git a/src/osx/Installer.Mac/resources/en.lproj/LICENSE b/src/osx/Installer.Mac/resources/en.lproj/LICENSE deleted file mode 120000 index 2a64f9d0f..000000000 --- a/src/osx/Installer.Mac/resources/en.lproj/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../../../../LICENSE \ No newline at end of file diff --git a/src/osx/Installer.Mac/resources/en.lproj/conclusion.html b/src/osx/Installer.Mac/resources/en.lproj/conclusion.html deleted file mode 100644 index 1fe9b197d..000000000 --- a/src/osx/Installer.Mac/resources/en.lproj/conclusion.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - -
-

Git Credential Manager was installed in /usr/local/share/gcm-core and configured for the current user.

-
-
-

Other users

-

- GCM has already been automatically configured for use by the current user with Git. - If other users wish to use GCM, have them run the following command to update their global Git configuration (~/.gitconfig): -

-

$ git-credential-manager configure

-

- To configure GCM for all users, run the following command to update the system Git configuration: -

-

$ git-credential-manager configure --system

-
-
-

Uninstall

-

If you wish to uninstall GCM, run the following script:

-

$ /usr/local/share/gcm-core/uninstall.sh

-
-
-

Resources

- -
- - diff --git a/src/osx/Installer.Mac/resources/en.lproj/welcome.html b/src/osx/Installer.Mac/resources/en.lproj/welcome.html deleted file mode 100644 index a5adeb79e..000000000 --- a/src/osx/Installer.Mac/resources/en.lproj/welcome.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - -
-

Git Credential Manager

-

- Git Credential Manager is a secure, cross-platform Git credential helper with authentication support for GitHub, Azure Repos, and other popular Git hosting services. -

-
-
-

Installation notes

-

- If you have the old Java-based Git Credential Manager for Mac & Linux installed through Homebrew, it will be unlinked after installation. -

-

- Git Credential Manager will be configured as the Git credential helper for the current user by updating the global Git configuration file (~/.gitconfig). -

-
-
-

Learn more

- -
- - diff --git a/src/osx/Installer.Mac/scripts/postinstall b/src/osx/Installer.Mac/scripts/postinstall deleted file mode 100755 index 9eed9aa7a..000000000 --- a/src/osx/Installer.Mac/scripts/postinstall +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -e - -PACKAGE=$1 -INSTALL_DESTINATION=$2 - -function IsBrewPkgInstalled -{ - # Check if Homebrew is installed - /usr/bin/which brew > /dev/null - if [ $? -eq 0 ] - then - # Check if the package has been installed - brew ls --versions "$1" > /dev/null - if [ $? -eq 0 ] - then - return 0 - fi - fi - return 1 -} - -# Check if Java GCM is present on this system and unlink it should it exist -if [ -L /usr/local/bin/git-credential-manager ] && IsBrewPkgInstalled "git-credential-manager"; -then - brew unlink git-credential-manager -fi - -# Create symlink to GCM in /usr/local/bin -mkdir -p /usr/local/bin -/bin/ln -Fs "$INSTALL_DESTINATION/git-credential-manager" /usr/local/bin/git-credential-manager - -# Configure GCM for the current user (running as the current user to avoid root -# from taking ownership of ~/.gitconfig) -sudo -u ${USER} "$INSTALL_DESTINATION/git-credential-manager" configure - -exit 0 diff --git a/src/osx/Installer.Mac/uninstall.sh b/src/osx/Installer.Mac/uninstall.sh deleted file mode 100755 index 55f6089a9..000000000 --- a/src/osx/Installer.Mac/uninstall.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" -GCMBIN="$THISDIR/git-credential-manager" - -# Ensure we're running as root -if [ $(id -u) != "0" ] -then - sudo "$0" "$@" - exit $? -fi - -# Unconfigure (as the current user) -echo "Unconfiguring credential helper..." -sudo -u `/usr/bin/logname` -E "$GCMBIN" unconfigure - -# Remove symlink -if [ -L /usr/local/bin/git-credential-manager ] -then - echo "Deleting symlink..." - rm /usr/local/bin/git-credential-manager -else - echo "No symlink found." -fi - -# Remove legacy symlink -if [ -L /usr/local/bin/git-credential-manager-core ] -then - echo "Deleting legacy symlink..." - rm /usr/local/bin/git-credential-manager-core -else - echo "No legacy symlink found." -fi - -# Forget package installation/delete receipt -echo "Removing installation receipt..." -pkgutil --forget com.microsoft.gitcredentialmanager - -# Remove application files -if [ -d "$THISDIR" ] -then - echo "Deleting application files..." - rm -rf "$THISDIR" -else - echo "No application files found." -fi diff --git a/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj b/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj deleted file mode 100644 index 9e768b91c..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/Atlassian.Bitbucket.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - false - true - latest - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - diff --git a/src/shared/Atlassian.Bitbucket.Tests/BitbucketAuthenticationTest.cs b/src/shared/Atlassian.Bitbucket.Tests/BitbucketAuthenticationTest.cs deleted file mode 100644 index a4a5e4afb..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/BitbucketAuthenticationTest.cs +++ /dev/null @@ -1,224 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using GitCredentialManager; -using GitCredentialManager.Tests.Objects; -using Moq; -using Xunit; - -namespace Atlassian.Bitbucket.Tests -{ - public class BitbucketAuthenticationTest - { - [Theory] - [InlineData("jsquire", "password")] - public async Task BitbucketAuthentication_GetCredentialsAsync_Basic_SucceedsAfterUserInput(string username, string password) - { - var context = new TestCommandContext(); - context.Terminal.Prompts["Username"] = username; - context.Terminal.SecretPrompts["Password"] = password; - Uri targetUri = null; - - var bitbucketAuthentication = new BitbucketAuthentication(context); - - var result = await bitbucketAuthentication.GetCredentialsAsync(targetUri, username, AuthenticationModes.Basic); - - Assert.NotNull(result); - Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode); - Assert.Equal(username, result.Credential.Account); - Assert.Equal(password, result.Credential.Password); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_OAuth_ReturnsOAuth() - { - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = true; // Allow OAuth mode - Uri targetUri = null; - - var bitbucketAuthentication = new BitbucketAuthentication(context); - - var result = await bitbucketAuthentication.GetCredentialsAsync(targetUri, null, AuthenticationModes.OAuth); - - Assert.NotNull(result); - Assert.Equal(AuthenticationModes.OAuth, result.AuthenticationMode); - Assert.Null(result.Credential); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_All_ShowsMenu_OAuthOption1() - { - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = true; // Allow OAuth mode - context.Settings.IsGuiPromptsEnabled = false; // Force text prompts - context.Terminal.Prompts["option (enter for default)"] = "1"; - Uri targetUri = null; - - var bitbucketAuthentication = new BitbucketAuthentication(context); - - var result = await bitbucketAuthentication.GetCredentialsAsync(targetUri, null, AuthenticationModes.All); - - Assert.NotNull(result); - Assert.Equal(AuthenticationModes.OAuth, result.AuthenticationMode); - Assert.Null(result.Credential); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_All_ShowsMenu_BasicOption2() - { - const string username = "jsquire"; - const string password = "password"; - - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = true; // Allow OAuth mode - context.Settings.IsGuiPromptsEnabled = false; // Force text prompts - context.Terminal.Prompts["option (enter for default)"] = "2"; - context.Terminal.Prompts["Username"] = username; - context.Terminal.SecretPrompts["Password"] = password; - Uri targetUri = null; - - var bitbucketAuthentication = new BitbucketAuthentication(context); - - var result = await bitbucketAuthentication.GetCredentialsAsync(targetUri, null, AuthenticationModes.All); - - Assert.NotNull(result); - Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode); - Assert.Equal(username, result.Credential.Account); - Assert.Equal(password, result.Credential.Password); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_All_NoDesktopSession_BasicOnly() - { - const string username = "jsquire"; - const string password = "password"; - - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = false; // Disallow OAuth mode - context.Terminal.Prompts["Username"] = username; - context.Terminal.SecretPrompts["Password"] = password; - Uri targetUri = null; - - var bitbucketAuthentication = new BitbucketAuthentication(context); - - var result = await bitbucketAuthentication.GetCredentialsAsync(targetUri, null, AuthenticationModes.All); - - Assert.NotNull(result); - Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode); - Assert.Equal(username, result.Credential.Account); - Assert.Equal(password, result.Credential.Password); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_AllModes_NoUser_BBCloud_HelperCmdLine() - { - var targetUri = new Uri("https://bitbucket.org"); - - var command = "/usr/bin/test-helper"; - var args = ""; - var expectedUserName = "jsquire"; - var expectedPassword = "password"; - var resultDict = new Dictionary - { - ["username"] = expectedUserName, - ["password"] = expectedPassword - }; - - string expectedArgs = $"prompt --show-basic --show-oauth"; - - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = true; // Enable OAuth and UI helper selection - - var authMock = new Mock(context) { CallBase = true }; - authMock.Setup(x => x.TryFindHelperCommand(out command, out args)) - .Returns(true); - authMock.Setup(x => x.InvokeHelperAsync(It.IsAny(), It.IsAny(), null, CancellationToken.None)) - .ReturnsAsync(resultDict); - - BitbucketAuthentication auth = authMock.Object; - CredentialsPromptResult result = await auth.GetCredentialsAsync(targetUri, null, AuthenticationModes.All); - - Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode); - Assert.Equal(result.Credential.Account, expectedUserName); - Assert.Equal(result.Credential.Password, expectedPassword); - - authMock.Verify(x => x.InvokeHelperAsync(command, expectedArgs, null, CancellationToken.None), - Times.Once); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_BasicOnly_User_BBCloud_HelperCmdLine() - { - var targetUri = new Uri("https://bitbucket.org"); - - var command = "/usr/bin/test-helper"; - var args = ""; - var expectedUserName = "jsquire"; - var expectedPassword = "password"; - var resultDict = new Dictionary - { - ["username"] = expectedUserName, - ["password"] = expectedPassword - }; - - string expectedArgs = $"prompt --username {expectedUserName} --show-basic"; - - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = true; // Enable UI helper selection - - var authMock = new Mock(context) { CallBase = true }; - authMock.Setup(x => x.TryFindHelperCommand(out command, out args)) - .Returns(true); - authMock.Setup(x => x.InvokeHelperAsync(It.IsAny(), It.IsAny(), null, CancellationToken.None)) - .ReturnsAsync(resultDict); - - BitbucketAuthentication auth = authMock.Object; - CredentialsPromptResult result = await auth.GetCredentialsAsync(targetUri, expectedUserName, AuthenticationModes.Basic); - - Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode); - Assert.Equal(result.Credential.Account, expectedUserName); - Assert.Equal(result.Credential.Password, expectedPassword); - - authMock.Verify(x => x.InvokeHelperAsync(command, expectedArgs, null, CancellationToken.None), - Times.Once); - } - - [Fact] - public async Task BitbucketAuthentication_GetCredentialsAsync_AllModes_NoUser_BBServerDC_HelperCmdLine() - { - var targetUri = new Uri("https://example.com/bitbucket"); - - var command = "/usr/bin/test-helper"; - var args = ""; - var expectedUserName = "jsquire"; - var expectedPassword = "password"; - var resultDict = new Dictionary - { - ["username"] = expectedUserName, - ["password"] = expectedPassword - }; - - string expectedArgs = $"prompt --url {targetUri} --show-basic --show-oauth"; - - var context = new TestCommandContext(); - context.SessionManager.IsDesktopSession = true; // Enable OAuth and UI helper selection - - var authMock = new Mock(context) { CallBase = true }; - authMock.Setup(x => x.TryFindHelperCommand(out command, out args)) - .Returns(true); - authMock.Setup(x => x.InvokeHelperAsync(It.IsAny(), It.IsAny(), null, CancellationToken.None)) - .ReturnsAsync(resultDict); - - BitbucketAuthentication auth = authMock.Object; - CredentialsPromptResult result = await auth.GetCredentialsAsync(targetUri, null, AuthenticationModes.All); - - Assert.Equal(AuthenticationModes.Basic, result.AuthenticationMode); - Assert.Equal(result.Credential.Account, expectedUserName); - Assert.Equal(result.Credential.Password, expectedPassword); - - authMock.Verify(x => x.InvokeHelperAsync(command, expectedArgs, null, CancellationToken.None), - Times.Once); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket.Tests/BitbucketHelperTest.cs b/src/shared/Atlassian.Bitbucket.Tests/BitbucketHelperTest.cs deleted file mode 100644 index cad371431..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/BitbucketHelperTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Xunit; - -namespace Atlassian.Bitbucket.Tests -{ - public class BitbucketHelperTest - { - [Theory] - [InlineData(null, false)] - [InlineData("", false)] - [InlineData(" ", false)] - [InlineData("bitbucket.org", true)] - [InlineData("BITBUCKET.ORG", true)] - [InlineData("BiTbUcKeT.OrG", true)] - [InlineData("bitbucket.example.com", false)] - [InlineData("bitbucket.example.org", false)] - [InlineData("bitbucket.org.com", false)] - [InlineData("bitbucket.org.org", false)] - public void BitbucketHelper_IsBitbucketOrg_StringHost(string str, bool expected) - { - bool actual = BitbucketHelper.IsBitbucketOrg(str); - Assert.Equal(expected, actual); - } - - [Theory] - [InlineData("http://bitbucket.org", true)] - [InlineData("https://bitbucket.org", true)] - [InlineData("http://bitbucket.org/path", true)] - [InlineData("https://bitbucket.org/path", true)] - [InlineData("http://BITBUCKET.ORG", true)] - [InlineData("https://BITBUCKET.ORG", true)] - [InlineData("http://BITBUCKET.ORG/PATH", true)] - [InlineData("https://BITBUCKET.ORG/PATH", true)] - [InlineData("http://BiTbUcKeT.OrG", true)] - [InlineData("https://BiTbUcKeT.OrG", true)] - [InlineData("http://BiTbUcKeT.OrG/pAtH", true)] - [InlineData("https://BiTbUcKeT.OrG/pAtH", true)] - [InlineData("http://bitbucket.example.com", false)] - [InlineData("https://bitbucket.example.com", false)] - [InlineData("http://bitbucket.example.com/path", false)] - [InlineData("https://bitbucket.example.com/path", false)] - [InlineData("http://bitbucket.example.org", false)] - [InlineData("https://bitbucket.example.org", false)] - [InlineData("http://bitbucket.example.org/path", false)] - [InlineData("https://bitbucket.example.org/path", false)] - [InlineData("http://bitbucket.org.com", false)] - [InlineData("https://bitbucket.org.com", false)] - [InlineData("http://bitbucket.org.com/path", false)] - [InlineData("https://bitbucket.org.com/path", false)] - [InlineData("http://bitbucket.org.org", false)] - [InlineData("https://bitbucket.org.org", false)] - [InlineData("http://bitbucket.org.org/path", false)] - [InlineData("https://bitbucket.org.org/path", false)] - public void BitbucketHelper_IsBitbucketOrg_Uri(string str, bool expected) - { - bool actual = BitbucketHelper.IsBitbucketOrg(new Uri(str)); - Assert.Equal(expected, actual); - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket.Tests/BitbucketHostProviderTest.cs b/src/shared/Atlassian.Bitbucket.Tests/BitbucketHostProviderTest.cs deleted file mode 100644 index 36b116615..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/BitbucketHostProviderTest.cs +++ /dev/null @@ -1,588 +0,0 @@ -using Atlassian.Bitbucket.Cloud; -using Atlassian.Bitbucket.DataCenter; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; -using GitCredentialManager.Tests.Objects; -using Moq; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Xunit; - -namespace Atlassian.Bitbucket.Tests -{ - public class BitbucketHostProviderTest - { - #region Tests - - private const string MOCK_ACCESS_TOKEN = "at-0987654321"; - private const string MOCK_ACCESS_TOKEN_ALT = "at-onetwothreefour-1234"; - private const string MOCK_EXPIRED_ACCESS_TOKEN = "at-1234567890-expired"; - private const string MOCK_REFRESH_TOKEN = "rt-1234567809"; - private const string BITBUCKET_DOT_ORG_HOST = "bitbucket.org"; - private const string DC_SERVER_HOST = "example.com"; - private Mock bitbucketAuthentication = new Mock(MockBehavior.Strict); - private Mock bitbucketApi = new Mock(MockBehavior.Strict); - - [Theory] - [InlineData("https", null, false)] - // We report that we support unencrypted HTTP here so that we can fail and - // show a helpful error message in the call to `GenerateCredentialAsync` instead. - [InlineData("http", BITBUCKET_DOT_ORG_HOST, true)] - [InlineData("ssh", BITBUCKET_DOT_ORG_HOST, false)] - [InlineData("https", BITBUCKET_DOT_ORG_HOST, true)] - [InlineData("https", "api.bitbucket.org", true)] // Currently does support sub domains. - - [InlineData("https", "bitbucket.ogg", false)] // No support of phony similar tld. - [InlineData("https", "bitbucket.com", false)] // No support of wrong tld. - [InlineData("https", DC_SERVER_HOST, false)] // No support of non bitbucket domains. - - [InlineData("http", "bitbucket.my-company-server.com", false)] // Currently no support for named on-premise instances - [InlineData("https", "my-company-server.com", false)] - [InlineData("https", "bitbucket.my.company.server.com", false)] - [InlineData("https", "api.bitbucket.my-company-server.com", false)] - [InlineData("https", "BITBUCKET.My-Company-Server.Com", false)] - public void BitbucketHostProvider_IsSupported(string protocol, string host, bool expected) - { - var input = new InputArguments(new Dictionary - { - ["protocol"] = protocol, - ["host"] = host, - }); - - var provider = new BitbucketHostProvider(new TestCommandContext()); - Assert.Equal(expected, provider.IsSupported(input)); - } - - [Theory] - [InlineData("Basic realm=\"Atlassian Bitbucket\"", true)] - [InlineData("Basic realm=\"GitSponge\"", false)] - public void BitbucketHostProvider_IsSupported_WWWAuth(string wwwauth, bool expected) - { - var input = new InputArguments(new Dictionary - { - ["wwwauth"] = wwwauth, - }); - - var provider = new BitbucketHostProvider(new TestCommandContext()); - Assert.Equal(expected, provider.IsSupported(input)); - } - - [Fact] - public void BitbucketHostProvider_IsSupported_FailsForNullInput() - { - InputArguments input = null; - var provider = new BitbucketHostProvider(new TestCommandContext()); - Assert.False(provider.IsSupported(input)); - } - - [Fact] - public void BitbucketHostProvider_IsSupported_FailsForNullHttpResponseMessage() - { - HttpResponseMessage httpResponseMessage = null; - var provider = new BitbucketHostProvider(new TestCommandContext()); - Assert.False(provider.IsSupported(httpResponseMessage)); - } - - [Theory] - [InlineData("X-AREQUESTID", "123456789", true)] // only the specific header is acceptable - [InlineData("X-REQUESTID", "123456789", false)] - [InlineData(null, null, false)] - public void BitbucketHostProvider_IsSupported_HttpResponseMessage(string header, string value, bool expected) - { - var input = new HttpResponseMessage(); - if (header != null) - { - input.Headers.Add(header, value); - } - - var provider = new BitbucketHostProvider(new TestCommandContext()); - Assert.Equal(expected, provider.IsSupported(input)); - } - - [Theory] - [InlineData("https", DC_SERVER_HOST, "jsquire", "password")] - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", "password")] - public async Task BitbucketHostProvider_GetCredentialAsync_Valid_Stored_Basic( - string protocol, string host, string username, string password) - { - InputArguments input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - if (DC_SERVER_HOST.Equals(host)) - { - MockDCSSOEnabled(); - } - MockStoredAccount(context, input, password); - MockRemoteBasicValid(input, password); - // HACK rebase MockRemoteBasicAuthAccountIsValidNo2FA(bitbucketApi, input, password, username); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(password, credential.Password); - - // Verify bitbucket.org credentials were validated - VerifyValidateBasicAuthCredentialsRan(input, password); - // Verify DC/Server credentials were not validated - - // Stored credentials so don't ask for more - VerifyInteractiveAuthNeverRan(); - } - - public Mock GetBitbucketApi() - { - return bitbucketApi; - } - - [Theory] - // Cloud - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", "password")] - public async Task BitbucketHostProvider_GetCredentialAsync_Valid_Stored_OAuth( - string protocol, string host, string username, string token) - { - InputArguments input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - if (DC_SERVER_HOST.Equals(host)) - { - MockDCSSOEnabled(); - } - MockStoredAccount(context, input, token); - MockRemoteAccessTokenValid(input, token); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(token, credential.Password); - - // Verify bitbucket.org credentials were validated - VerifyValidateAccessTokenRan(input, token); - - // Stored credentials so don't ask for more - VerifyInteractiveAuthNeverRan(); - } - - private void MockDCSSOEnabled() - { - bitbucketApi.Setup(ba => ba.GetAuthenticationMethodsAsync()).Returns(Task.FromResult(new List(){AuthenticationMethod.BasicAuth, AuthenticationMethod.Sso})); - bitbucketApi.Setup(ba => ba.IsOAuthInstalledAsync()).Returns(Task.FromResult(true)); - } - - [Theory] - // DC - [InlineData("https", DC_SERVER_HOST, "jsquire", "password")] - // Cloud - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", "password")] - public async Task BitbucketHostProvider_GetCredentialAsync_Valid_New_Basic( - string protocol, string host, string username, string password) - { - InputArguments input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - MockPromptBasic(input, password); - MockRemoteBasicValid(input, password); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(password, credential.Password); - - VerifyInteractiveAuthRan(input); - } - - [Theory] - // DC/Server does not currently support OAuth - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", MOCK_REFRESH_TOKEN, MOCK_ACCESS_TOKEN)] - public async Task BitbucketHostProvider_GetCredentialAsync_Valid_New_OAuth( - string protocol, string host, string username, string refreshToken, string accessToken) - { - InputArguments input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - MockPromptOAuth(input); - MockRemoteOAuthTokenCreate(input, accessToken, refreshToken); - MockRemoteAccessTokenValid(input, accessToken); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(accessToken, credential.Password); - - VerifyInteractiveAuthRan(input); - VerifyOAuthFlowRan(input, accessToken); - VerifyValidateAccessTokenRan(input, accessToken); - VerifyOAuthRefreshTokenStored(context, input, refreshToken); - } - - [Theory] - // DC/Server does not currently support OAuth - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", MOCK_REFRESH_TOKEN, MOCK_ACCESS_TOKEN)] - public async Task BitbucketHostProvider_GetCredentialAsync_MissingAT_OAuth_Refresh( - string protocol, string host, string username, string refreshToken, string accessToken) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - // AT has does not exist, but RT is still valid - MockStoredRefreshToken(context, input, refreshToken); - MockRemoteAccessTokenValid(input, accessToken); - MockRemoteRefreshTokenValid(input, refreshToken, accessToken); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(accessToken, credential.Password); - - VerifyValidateAccessTokenRan(input, accessToken); - VerifyOAuthRefreshRan(input, refreshToken); - VerifyInteractiveAuthNeverRan(); - } - - [Theory] - // DC/Server does not currently support OAuth - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", MOCK_REFRESH_TOKEN, MOCK_EXPIRED_ACCESS_TOKEN, MOCK_ACCESS_TOKEN)] - public async Task BitbucketHostProvider_GetCredentialAsync_ExpiredAT_OAuth_Refresh( - string protocol, string host, string username, string refreshToken, string expiredAccessToken, string accessToken) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - // AT exists but has expired, but RT is still valid - MockStoredAccount(context, input, expiredAccessToken); - MockRemoteAccessTokenExpired(input, expiredAccessToken); - - MockStoredRefreshToken(context, input, refreshToken); - MockRemoteAccessTokenValid(input, accessToken); - MockRemoteRefreshTokenValid(input, refreshToken, accessToken); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(accessToken, credential.Password); - - VerifyValidateAccessTokenRan(input, accessToken); - VerifyOAuthRefreshRan(input, refreshToken); - VerifyInteractiveAuthNeverRan(); - } - - [Theory] - // Cloud - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", MOCK_REFRESH_TOKEN, MOCK_ACCESS_TOKEN)] - public async Task BitbucketHostProvider_GetCredentialAsync_PreconfiguredMode_OAuth_ValidRT_IsRespected( - string protocol, string host, string username, string refreshToken, string accessToken) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - context.Environment.Variables.Add(BitbucketConstants.EnvironmentVariables.AuthenticationModes, "oauth"); - - // We have a stored RT so we can just use that without any prompts - MockStoredRefreshToken(context, input, refreshToken); - MockRemoteAccessTokenValid(input, accessToken); - MockRemoteRefreshTokenValid(input, refreshToken, accessToken); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.NotNull(credential); - - VerifyInteractiveAuthNeverRan(); - VerifyOAuthRefreshRan(input, refreshToken); - } - - [Theory] - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", MOCK_ACCESS_TOKEN, MOCK_ACCESS_TOKEN_ALT, MOCK_REFRESH_TOKEN)] - public async Task BitbucketHostProvider_GetCredentialAsync_AlwaysRefreshCredentials_OAuth_IsRespected( - string protocol, string host, string username, string storedToken, string newToken, string refreshToken) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - context.Environment.Variables.Add( - BitbucketConstants.EnvironmentVariables.AlwaysRefreshCredentials, bool.TrueString); - - // User has stored access token that we shouldn't use - RT should be used to mint new AT - MockStoredAccount(context, input, storedToken); - MockStoredRefreshToken(context, input, refreshToken); - MockRemoteAccessTokenValid(input, newToken); - MockRemoteRefreshTokenValid(input, refreshToken, newToken); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(newToken, credential.Password); - - VerifyInteractiveAuthNeverRan(); - VerifyOAuthRefreshRan(input, refreshToken); - } - - [Theory] - // Cloud - [InlineData("https", BITBUCKET_DOT_ORG_HOST, "jsquire", "old-password", "new-password")] - // DC - [InlineData("https", DC_SERVER_HOST, "jsquire", "old-password", "new-password")] - public async Task BitbucketHostProvider_GetCredentialAsync_AlwaysRefreshCredentials_Basic_IsRespected( - string protocol, string host, string username, string storedPassword, string freshPassword) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - context.Environment.Variables.Add( - BitbucketConstants.EnvironmentVariables.AlwaysRefreshCredentials, bool.TrueString); - - // User has stored password that we shouldn't use - MockStoredAccount(context, input, storedPassword); - MockPromptBasic(input, freshPassword); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - var credential = await provider.GetCredentialAsync(input); - - Assert.Equal(username, credential.Account); - Assert.Equal(freshPassword, credential.Password); - - VerifyInteractiveAuthRan(input); - } - - [Theory] - // DC - supports Basic, OAuth - [InlineData("https", "example.com", "basic", AuthenticationModes.Basic)] - [InlineData("https", "example.com", "oauth", AuthenticationModes.OAuth)] - [InlineData("https", "example.com", "NOT-A-REAL-VALUE", DataCenterConstants.ServerAuthenticationModes)] - [InlineData("https", "example.com", "none", DataCenterConstants.ServerAuthenticationModes)] - [InlineData("https", "example.com", null, DataCenterConstants.ServerAuthenticationModes)] - // Cloud - supports Basic, OAuth - [InlineData("https", "bitbucket.org", "oauth", AuthenticationModes.OAuth)] - [InlineData("https", "bitbucket.org", "basic", AuthenticationModes.Basic)] - [InlineData("https", "bitbucket.org", "NOT-A-REAL-VALUE", CloudConstants.DotOrgAuthenticationModes)] - [InlineData("https", "bitbucket.org", "none", CloudConstants.DotOrgAuthenticationModes)] - [InlineData("https", "bitbucket.org", null, CloudConstants.DotOrgAuthenticationModes)] - public async Task BitbucketHostProvider_GetSupportedAuthenticationModes(string protocol, string host, string bitbucketAuthModes, AuthenticationModes expectedModes) - { - var input = MockInput(protocol, host, null); - - var context = new TestCommandContext(); - if (bitbucketAuthModes != null) - { - context.Environment.Variables.Add(BitbucketConstants.EnvironmentVariables.AuthenticationModes, bitbucketAuthModes); - } - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - AuthenticationModes actualModes = await provider.GetSupportedAuthenticationModesAsync(input); - - Assert.Equal(expectedModes, actualModes); - } - - [Theory] - [InlineData("https", DC_SERVER_HOST, "jsquire")] - public async Task BitbucketHostProvider_StoreCredentialAsync(string protocol, string host, string username) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - Assert.Equal(0, context.CredentialStore.Count); - - await provider.StoreCredentialAsync(input); - - Assert.Equal(1, context.CredentialStore.Count); - } - - [Theory] - [InlineData("https", DC_SERVER_HOST, "jsquire", "password")] - public async Task BitbucketHostProvider_EraseCredentialAsync(string protocol, string host, string username, string password) - { - var input = MockInput(protocol, host, username); - - var context = new TestCommandContext(); - - MockStoredAccount(context, input, password); - - var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, MockRestApiRegistry(input, bitbucketApi).Object); - - Assert.Equal(1, context.CredentialStore.Count); - - await provider.EraseCredentialAsync(input); - - Assert.Equal(0, context.CredentialStore.Count); - } - - #endregion - - #region Test helpers - - private static InputArguments MockInput(string protocol, string host, string username) - { - return new InputArguments(new Dictionary - { - ["protocol"] = protocol, - ["host"] = host, - ["username"] = username - }); - } - - private void VerifyOAuthFlowRan(InputArguments input, string token) - { - // Get new access token and refresh token - bitbucketAuthentication.Verify(m => m.CreateOAuthCredentialsAsync(input), Times.Once); - - // Check access token works/resolve username - bitbucketApi.Verify(m => m.GetUserInformationAsync(null, token, true), Times.Once); - } - - private void VerifyValidateBasicAuthCredentialsNeverRan() - { - // Never check username/password works - bitbucketApi.Verify(m => m.GetUserInformationAsync(It.IsAny(), It.IsAny(), false), Times.Never); - } - - private void VerifyValidateBasicAuthCredentialsRan(InputArguments input, string password) - { - // Check username/password works - bitbucketApi.Verify(m => m.GetUserInformationAsync(input.UserName, password, false), Times.Once); - } - - private void VerifyValidateAccessTokenRan(InputArguments input, string token) - { - // Check tokens works - bitbucketApi.Verify(m => m.GetUserInformationAsync(null, token, true), Times.Once); - } - - private void VerifyInteractiveAuthRan(InputArguments input) - { - var remoteUri = input.GetRemoteUri(); - - bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny()), Times.Once); - } - - private void VerifyInteractiveAuthNeverRan() - { - bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - } - - private void VerifyOAuthRefreshRan(InputArguments input, string refreshToken) - { - // Check refresh was called - bitbucketAuthentication.Verify(m => m.RefreshOAuthCredentialsAsync(input, refreshToken), Times.Once); - } - - private void MockRemoteRefreshTokenValid(InputArguments input, string refreshToken, string accessToken) - { - bitbucketAuthentication.Setup(m => m.RefreshOAuthCredentialsAsync(input, refreshToken)).ReturnsAsync(new OAuth2TokenResult(accessToken, "access_token")); - } - - private void MockPromptBasic(InputArguments input, string password) - { - var remoteUri = input.GetRemoteUri(); - bitbucketAuthentication.Setup(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny())) - .ReturnsAsync(new CredentialsPromptResult(AuthenticationModes.Basic, new TestCredential(input.Host, input.UserName, password))); - } - - private void MockPromptOAuth(InputArguments input) - { - var remoteUri = input.GetRemoteUri(); - bitbucketAuthentication.Setup(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny())) - .ReturnsAsync(new CredentialsPromptResult(AuthenticationModes.OAuth)); - } - - private void MockRemoteBasicValid(InputArguments input, string password) - { - var userInfo = new Mock(MockBehavior.Strict); - userInfo.Setup(ui => ui.UserName).Returns(input.UserName); - - // Basic - bitbucketApi.Setup(x => x.GetUserInformationAsync(input.UserName, password, false)) - .ReturnsAsync(new RestApiResult(System.Net.HttpStatusCode.OK, userInfo.Object)); - } - - private void MockRemoteAccessTokenExpired(InputArguments input, string token) - { - // OAuth - bitbucketApi.Setup(x => x.GetUserInformationAsync(null, token, true)) - .ReturnsAsync(new RestApiResult(System.Net.HttpStatusCode.Unauthorized)); - } - - private void MockRemoteAccessTokenValid(InputArguments input, string token) - { - var userInfo = new Mock(MockBehavior.Strict); - userInfo.Setup(ui => ui.UserName).Returns(input.UserName); - - // OAuth - bitbucketApi.Setup(x => x.GetUserInformationAsync(null, token, true)) - .ReturnsAsync(new RestApiResult(System.Net.HttpStatusCode.OK, userInfo.Object)); - } - - private static void MockRemoteOAuthAccountIsInvalid(Mock bitbucketApi) - { - // OAuth - bitbucketApi.Setup(x => x.GetUserInformationAsync(null, It.IsAny(), true)).ReturnsAsync(new RestApiResult(System.Net.HttpStatusCode.BadRequest)); - } - - private static void MockStoredAccount(TestCommandContext context, InputArguments input, string password) - { - var remoteUri = input.GetRemoteUri(); - var remoteUrl = remoteUri.AbsoluteUri.Substring(0, remoteUri.AbsoluteUri.Length - 1); - context.CredentialStore.Add(remoteUrl, new TestCredential(input.Host, input.UserName, password)); - } - - private static void MockStoredRefreshToken(TestCommandContext context, InputArguments input, string token) - { - var remoteUri = input.GetRemoteUri(); - var refreshService = BitbucketHostProvider.GetRefreshTokenServiceName(remoteUri); - context.CredentialStore.Add(refreshService, new TestCredential(refreshService, input.UserName, token)); - } - - private void MockRemoteOAuthTokenCreate(InputArguments input, string accessToken, string refreshToken) - { - bitbucketAuthentication.Setup(x => x.CreateOAuthCredentialsAsync(input)) - .ReturnsAsync(new OAuth2TokenResult(accessToken, "access_token") { RefreshToken = refreshToken }); - } - - private void VerifyOAuthRefreshTokenStored(TestCommandContext context, InputArguments input, string refreshToken) - { - var remoteUri = input.GetRemoteUri(); - string refreshService = BitbucketHostProvider.GetRefreshTokenServiceName(remoteUri); - bool result = context.CredentialStore.TryGet(refreshService, input.UserName, out var credential); - - Assert.True(result); - Assert.Equal(refreshToken, credential.Password); - } - - private static Mock> MockRestApiRegistry(InputArguments input, Mock bitbucketApi) - { - var restApiRegistry = new Mock>(MockBehavior.Strict); - - restApiRegistry.Setup(rar => rar.Get(input)).Returns(bitbucketApi.Object); - - return restApiRegistry; - } - - #endregion - } -} diff --git a/src/shared/Atlassian.Bitbucket.Tests/BitbucketRestApiRegistryTest.cs b/src/shared/Atlassian.Bitbucket.Tests/BitbucketRestApiRegistryTest.cs deleted file mode 100644 index ee4499dfb..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/BitbucketRestApiRegistryTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using GitCredentialManager; -using Moq; -using Xunit; - -namespace Atlassian.Bitbucket.Tests -{ - public class BitbucketRestApiRegistryTest - { - private Mock context = new Mock(MockBehavior.Strict); - private Mock settings = new Mock(MockBehavior.Strict); - - [Fact] - public void BitbucketRestApiRegistry_Get_ReturnsCloudApi_ForBitbucketOrg() - { - // Given - settings.Setup(s => s.RemoteUri).Returns(new System.Uri("https://bitbucket.org")); - context.Setup(c => c.Settings).Returns(settings.Object); - - var input = new InputArguments(new Dictionary - { - ["protocol"] = "https", - ["host"] = "bitbucket.org", - }); - - // When - var registry = new BitbucketRestApiRegistry(context.Object); - var api = registry.Get(input); - - // Then - Assert.NotNull(api); - Assert.IsType(api); - - } - - [Fact] - public void BitbucketRestApiRegistry_Get_ReturnsDataCenterApi_ForBitbucketDC() - { - // Given - settings.Setup(s => s.RemoteUri).Returns(new System.Uri("https://example.com")); - context.Setup(c => c.Settings).Returns(settings.Object); - - var input = new InputArguments(new Dictionary - { - ["protocol"] = "http", - ["host"] = "example.com", - }); - - // When - var registry = new BitbucketRestApiRegistry(context.Object); - var api = registry.Get(input); - - // Then - Assert.NotNull(api); - Assert.IsType(api); - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket.Tests/BitbucketTokenEndpointResponseJsonTest.cs b/src/shared/Atlassian.Bitbucket.Tests/BitbucketTokenEndpointResponseJsonTest.cs deleted file mode 100644 index f9ac14b9a..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/BitbucketTokenEndpointResponseJsonTest.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Text.Json; -using Xunit; - -namespace Atlassian.Bitbucket.Tests -{ - public class BitbucketTokenEndpointResponseJsonTest - { - [Fact] - public void BitbucketTokenEndpointResponseJson_Deserialize_Uses_Scopes() - { - var accessToken = "123"; - var tokenType = "Bearer"; - var expiresIn = 1000; - var scopesString = "x,y,z"; - var scopeString = "a,b,c"; - - var json = $"{{\"access_token\": \"{accessToken}\", \"token_type\": \"{tokenType}\", \"expires_in\": {expiresIn}, \"scopes\": \"{scopesString}\", \"scope\": \"{scopeString}\"}}"; - - var result = JsonSerializer.Deserialize(json, - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - - Assert.Equal(accessToken, result.AccessToken); - Assert.Equal(tokenType, result.TokenType); - Assert.Equal(expiresIn, result.ExpiresIn); - Assert.Equal(scopesString, result.Scope); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs b/src/shared/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs deleted file mode 100644 index 1a6866fb6..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/Cloud/BitbucketOAuth2ClientTest.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Atlassian.Bitbucket.Cloud; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; -using GitCredentialManager.Tests.Objects; -using Moq; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.Cloud -{ - public class BitbucketOAuth2ClientTest - { - private Mock httpClient = new Mock(MockBehavior.Strict); - private Mock settings = new Mock(MockBehavior.Loose); - private Mock browser = new Mock(MockBehavior.Strict); - private Mock codeGenerator = new Mock(MockBehavior.Strict); - private IEnumerable scopes = new List(); - private CancellationToken ct = new CancellationToken(); - private Uri rootCallbackUri = new Uri("http://localhost:34106/"); - private string nonce = "12345"; - private string pkceCodeVerifier = "abcde"; - private string pkceCodeChallenge = "xyz987"; - private string authorization_code = "authorization_token"; - - [Fact] - public async Task BitbucketOAuth2Client_GetAuthorizationCodeAsync_ReturnsCode() - { - MockClientIdOverride(false, "never used"); - - Uri finalCallbackUri = MockFinalCallbackUri(); - - Bitbucket.Cloud.BitbucketOAuth2Client client = GetBitbucketOAuth2Client(); - - MockGetAuthenticationCodeAsync(finalCallbackUri, null, client.Scopes); - - MockCodeGenerator(); - - var result = await client.GetAuthorizationCodeAsync(browser.Object, ct); - - VerifyAuthorizationCodeResult(result); - } - - [Theory] - [InlineData(null)] - [InlineData("i234")] - public async Task BitbucketOAuth2Client_GetAuthorizationCodeAsync_RespectsClientIdOverride_ReturnsCode(string clientId) - { - MockClientIdOverride(clientId != null, clientId); - - Uri finalCallbackUri = MockFinalCallbackUri(); - - Bitbucket.Cloud.BitbucketOAuth2Client client = GetBitbucketOAuth2Client(); - - MockGetAuthenticationCodeAsync(finalCallbackUri, clientId, client.Scopes); - - MockCodeGenerator(); - - var result = await client.GetAuthorizationCodeAsync(browser.Object, ct); - - VerifyAuthorizationCodeResult(result); - } - - [Fact] - public async Task BitbucketOAuth2Client_GetDeviceCodeAsync() - { - var trace2 = new NullTrace2(); - var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); - await Assert.ThrowsAsync(async () => await client.GetDeviceCodeAsync(scopes, ct)); - } - - [Theory] - [InlineData("https", "example.com", "john", "https://example.com/refresh_token")] - [InlineData("http", "example.com", "john", "http://example.com/refresh_token")] - [InlineData("https", "example.com", "dave", "https://example.com/refresh_token")] - [InlineData("https", "example.com/", "john", "https://example.com/refresh_token")] - public void BitbucketOAuth2Client_GetRefreshTokenServiceName(string protocol, string host, string username, string expectedResult) - { - var trace2 = new NullTrace2(); - var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); - var input = new InputArguments(new Dictionary - { - ["protocol"] = protocol, - ["host"] = host, - ["username"] = username - }); - Assert.Equal(expectedResult, client.GetRefreshTokenServiceName(input)); - } - - - private void VerifyAuthorizationCodeResult(OAuth2AuthorizationCodeResult result) - { - Assert.NotNull(result); - Assert.Equal(authorization_code, result.Code); - Assert.Equal(rootCallbackUri, result.RedirectUri); - Assert.Equal(pkceCodeVerifier, result.CodeVerifier); - } - - private Bitbucket.Cloud.BitbucketOAuth2Client GetBitbucketOAuth2Client() - { - var trace2 = new NullTrace2(); - var client = new Bitbucket.Cloud.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); - client.CodeGenerator = codeGenerator.Object; - return client; - } - - private void MockCodeGenerator() - { - codeGenerator.Setup(c => c.CreateNonce()).Returns(nonce); - codeGenerator.Setup(c => c.CreatePkceCodeVerifier()).Returns(pkceCodeVerifier); - codeGenerator.Setup(c => c.CreatePkceCodeChallenge(OAuth2PkceChallengeMethod.Sha256, pkceCodeVerifier)).Returns(pkceCodeChallenge); - } - - private void MockGetAuthenticationCodeAsync(Uri finalCallbackUri, string overrideClientId, IEnumerable scopes) - { - var authorizationUri = new UriBuilder(CloudConstants.OAuth2AuthorizationEndpoint) - { - Query = "?response_type=code" - + "&client_id=" + (overrideClientId ?? CloudConstants.OAuth2ClientId) - + "&state=12345" - + "&code_challenge_method=" + OAuth2Constants.AuthorizationEndpoint.PkceChallengeMethodS256 - + "&code_challenge=" + WebUtility.UrlEncode(pkceCodeChallenge).ToLower() - + "&redirect_uri=" + WebUtility.UrlEncode(rootCallbackUri.AbsoluteUri).ToLower() - + "&scope=" + WebUtility.UrlEncode(string.Join(" ", scopes)).ToLower() - }.Uri; - - browser.Setup(b => b.GetAuthenticationCodeAsync(authorizationUri, rootCallbackUri, ct)).Returns(Task.FromResult(finalCallbackUri)); - } - - private Uri MockFinalCallbackUri() - { - var finalUri = new Uri(rootCallbackUri, "?state=" + nonce + "&code=" + authorization_code); - browser.Setup(b => b.UpdateRedirectUri(rootCallbackUri)).Returns(rootCallbackUri); - return finalUri; - } - - private string MockeClientIdOverride(bool set) - { - return MockClientIdOverride(set, null); - } - private string MockClientIdOverride(bool set, string value) - { - settings.Setup(s => s.TryGetSetting( - CloudConstants.EnvironmentVariables.OAuthClientId, - Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientId, - out value)).Returns(set); - return value; - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket.Tests/Cloud/BitbucketRestApiTest.cs b/src/shared/Atlassian.Bitbucket.Tests/Cloud/BitbucketRestApiTest.cs deleted file mode 100644 index 7930907ab..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/Cloud/BitbucketRestApiTest.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Atlassian.Bitbucket.Cloud; -using GitCredentialManager.Tests; -using GitCredentialManager.Tests.Objects; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.Cloud -{ - public class BitbucketRestApiTest - { - [Theory] - [InlineData("jsquire", "token", true)] - [InlineData("jsquire", "password", false)] - public async Task BitbucketRestApi_GetUserInformationAsync_ReturnsUserInfo_ForSuccessfulRequest(string username, string password, bool isBearerToken) - { - var uuid = Guid.NewGuid(); - var accountId = "1234"; - - var context = new TestCommandContext(); - - var expectedRequestUri = new Uri("https://api.bitbucket.org/2.0/user"); - - var userinfoResponseJson = $"{{\"username\":\"{username}\",\"has_2fa_enabled\":false,\"account_id\":\"{accountId}\",\"uuid\":\"{uuid}\"}}"; - - var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(userinfoResponseJson) - }; - - var httpHandler = new TestHttpMessageHandler(); - httpHandler.Setup(HttpMethod.Get, expectedRequestUri, request => - { - if (isBearerToken) - { - RestTestUtilities.AssertBearerAuth(request, password); - } - else - { - RestTestUtilities.AssertBasicAuth(request, username, password); - } - - return httpResponse; - }); - context.HttpClientFactory.MessageHandler = httpHandler; - - var api = new BitbucketRestApi(context); - var result = await api.GetUserInformationAsync(username, password, isBearerToken); - - Assert.NotNull(result); - Assert.Equal(username, result.Response.UserName); - - httpHandler.AssertRequest(HttpMethod.Get, expectedRequestUri, 1); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket.Tests/Cloud/UserInfoTest.cs b/src/shared/Atlassian.Bitbucket.Tests/Cloud/UserInfoTest.cs deleted file mode 100644 index 1fa039898..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/Cloud/UserInfoTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Atlassian.Bitbucket.Cloud; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.Cloud -{ - public class UserInfoTest - { - [Fact] - public void UserInfo_Set() - { - var userInfo = new UserInfo() - { - UserName = "123", - }; - - Assert.Equal("123", userInfo.UserName); - } - - [Fact] - public void Deserialize_UserInfo() - { - var uuid = "{bef4bd75-03fe-4f19-9c6c-ed57b05ab6f6}"; - var userName = "bob"; - var accountId = "123abc"; - - var json = $"{{\"uuid\": \"{uuid}\", \"has_2fa_enabled\": null, \"username\": \"{userName}\", \"account_id\": \"{accountId}\"}}"; - - var result = JsonSerializer.Deserialize(json, new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - }); - - Assert.Equal(userName, result.UserName); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs b/src/shared/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs deleted file mode 100644 index e2e7225db..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/BitbucketOAuth2ClientTest.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Atlassian.Bitbucket.DataCenter; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; -using GitCredentialManager.Tests.Objects; -using Moq; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.DataCenter -{ - public class BitbucketOAuth2ClientTest - { - private Mock httpClient = new Mock(MockBehavior.Strict); - private Mock settings = new Mock(MockBehavior.Loose); - private Mock browser = new Mock(MockBehavior.Strict); - private Mock codeGenerator = new Mock(MockBehavior.Strict); - private CancellationToken ct = new CancellationToken(); - private Uri rootCallbackUri = new Uri("http://localhost:34106/"); - private string nonce = "12345"; - private string pkceCodeVerifier = "abcde"; - private string pkceCodeChallenge = "xyz987"; - private string authorization_code = "authorization_token"; - - [Fact] - public async Task BitbucketOAuth2Client_GetAuthorizationCodeAsync_ReturnsCode() - { - var remoteUrl = MockRemoteUri("http://example.com"); - var clientId = MockClientIdOverride("dc-client-id"); - MockClientSecretOverride("dc-client-seccret"); - - Uri finalCallbackUri = MockFinalCallbackUri(rootCallbackUri); - - var client = GetBitbucketOAuth2Client(); - - MockGetAuthenticationCodeAsync(remoteUrl, rootCallbackUri, finalCallbackUri, clientId, client.Scopes); - - MockCodeGenerator(); - - var result = await client.GetAuthorizationCodeAsync(browser.Object, ct); - - VerifyAuthorizationCodeResult(result, rootCallbackUri); - } - - [Fact] - public async Task BitbucketOAuth2Client_GetAuthorizationCodeAsync_ReturnsCode_WhileRespectingRedirectUriOverride() - { - var rootCallbackUrl = MockRootCallbackUriOverride("http://localhost:12345/"); - var remoteUrl = MockRemoteUri("http://example.com"); - var clientId = MockClientIdOverride("dc-client-id"); - MockClientSecretOverride("dc-client-seccret"); - - Uri finalCallbackUri = MockFinalCallbackUri(new Uri(rootCallbackUrl)); - - var client = GetBitbucketOAuth2Client(); - - MockGetAuthenticationCodeAsync(remoteUrl, new Uri(rootCallbackUrl), finalCallbackUri, clientId, client.Scopes); - - MockCodeGenerator(); - - var result = await client.GetAuthorizationCodeAsync(browser.Object, ct); - - VerifyAuthorizationCodeResult(result, new Uri(rootCallbackUrl)); - } - - private void VerifyAuthorizationCodeResult(OAuth2AuthorizationCodeResult result, Uri redirectUri) - { - Assert.NotNull(result); - Assert.Equal(authorization_code, result.Code); - Assert.Equal(redirectUri, result.RedirectUri); - Assert.Equal(pkceCodeVerifier, result.CodeVerifier); - } - - private Bitbucket.DataCenter.BitbucketOAuth2Client GetBitbucketOAuth2Client() - { - var trace2 = new NullTrace2(); - var client = new Bitbucket.DataCenter.BitbucketOAuth2Client(httpClient.Object, settings.Object, trace2); - client.CodeGenerator = codeGenerator.Object; - return client; - } - - private void MockCodeGenerator() - { - codeGenerator.Setup(c => c.CreateNonce()).Returns(nonce); - codeGenerator.Setup(c => c.CreatePkceCodeVerifier()).Returns(pkceCodeVerifier); - codeGenerator.Setup(c => c.CreatePkceCodeChallenge(OAuth2PkceChallengeMethod.Sha256, pkceCodeVerifier)).Returns(pkceCodeChallenge); - } - - private void MockGetAuthenticationCodeAsync(string url, Uri redirectUri, Uri finalCallbackUri, string overrideClientId, IEnumerable scopes) - { - var authorizationUri = new UriBuilder(url + "/rest/oauth2/latest/authorize") - { - Query = "?response_type=code" - + "&client_id=" + (overrideClientId ?? "clientId") - + "&state=12345" - + "&code_challenge_method=" + OAuth2Constants.AuthorizationEndpoint.PkceChallengeMethodS256 - + "&code_challenge=" + WebUtility.UrlEncode(pkceCodeChallenge).ToLower() - + "&redirect_uri=" + WebUtility.UrlEncode(redirectUri.AbsoluteUri).ToLower() - + "&scope=" + WebUtility.UrlEncode(string.Join(" ", scopes)).ToUpper() - }.Uri; - - browser.Setup(b => b.GetAuthenticationCodeAsync(authorizationUri, redirectUri, ct)).Returns(Task.FromResult(finalCallbackUri)); - } - - private Uri MockFinalCallbackUri(Uri redirectUri) - { - var finalUri = new Uri(rootCallbackUri, "?state=" + nonce + "&code=" + authorization_code); - // This is a simplification but consistent - browser.Setup(b => b.UpdateRedirectUri(redirectUri)).Returns(redirectUri); - return finalUri; - } - - private string MockRemoteUri(string value) - { - settings.Setup(s => s.RemoteUri).Returns(new Uri(value)); - return value; - } - - private string MockClientIdOverride(string value) - { - settings.Setup(s => s.TryGetSetting( - DataCenterConstants.EnvironmentVariables.OAuthClientId, - Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientId, - out value)).Returns(true); - return value; - } - - private string MockClientSecretOverride(string value) - { - settings.Setup(s => s.TryGetSetting( - DataCenterConstants.EnvironmentVariables.OAuthClientSecret, - Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientSecret, - out value)).Returns(true); - return value; - } - - private string MockRootCallbackUriOverride(string value) - { - settings.Setup(s => s.TryGetSetting( - DataCenterConstants.EnvironmentVariables.OAuthRedirectUri, - Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthRedirectUri, - out value)).Returns(true); - return value; - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/BitbucketRestApiTest.cs b/src/shared/Atlassian.Bitbucket.Tests/DataCenter/BitbucketRestApiTest.cs deleted file mode 100644 index d332b71ae..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/BitbucketRestApiTest.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Atlassian.Bitbucket.DataCenter; -using GitCredentialManager.Tests.Objects; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.DataCenter -{ - public class BitbucketRestApiTest - { - [Fact] - public async Task BitbucketRestApi_GetUserInformationAsync_ReturnsUserInfo_ForSuccessfulRequest_DoesNothing() - { - var context = new TestCommandContext(); - - var expectedRequestUri = new Uri("http://example.com/rest/api/1.0/users"); - var httpHandler = new TestHttpMessageHandler(); - var httpResponse = new HttpResponseMessage(HttpStatusCode.OK); - httpHandler.Setup(HttpMethod.Get, expectedRequestUri, request => - { - return httpResponse; - }); - context.HttpClientFactory.MessageHandler = httpHandler; - - context.Settings.RemoteUri = new Uri("http://example.com"); - - var api = new BitbucketRestApi(context); - var result = await api.GetUserInformationAsync("never used", "never used", false); - - Assert.NotNull(result); - Assert.Equal(DataCenterConstants.OAuthUserName, result.Response.UserName); - - httpHandler.AssertRequest(HttpMethod.Get, expectedRequestUri, 1); - } - - [Theory] - [InlineData(HttpStatusCode.Unauthorized, true)] - [InlineData(HttpStatusCode.NotFound, false)] - public async Task BitbucketRestApi_IsOAuthInstalledAsync_ReflectsBitbucketAuthenticationResponse(HttpStatusCode responseCode, bool impliedSupport) - { - var context = new TestCommandContext(); - var httpHandler = new TestHttpMessageHandler(); - - var expectedRequestUri = new Uri("http://example.com/rest/oauth2/1.0/client"); - - var httpResponse = new HttpResponseMessage(responseCode); - httpHandler.Setup(HttpMethod.Get, expectedRequestUri, request => - { - return httpResponse; - }); - - context.HttpClientFactory.MessageHandler = httpHandler; - context.Settings.RemoteUri = new Uri("http://example.com"); - - var api = new BitbucketRestApi(context); - - var isInstalled = await api.IsOAuthInstalledAsync(); - - httpHandler.AssertRequest(HttpMethod.Get, expectedRequestUri, 1); - - Assert.Equal(impliedSupport, isInstalled); - } - - [Theory] - [MemberData(nameof(GetAuthenticationMethodsAsyncData))] - public async Task BitbucketRestApi_GetAuthenticationMethodsAsync_ReflectRestApiResponse(string loginOptionResponseJson, List impliedSupportedMethods, List impliedUnsupportedMethods) - { - var context = new TestCommandContext(); - var httpHandler = new TestHttpMessageHandler(); - - var expectedRequestUri = new Uri("http://example.com/rest/authconfig/1.0/login-options"); - - var httpResponse = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(loginOptionResponseJson) - }; - - httpHandler.Setup(HttpMethod.Get, expectedRequestUri, request => - { - return httpResponse; - }); - - context.HttpClientFactory.MessageHandler = httpHandler; - context.Settings.RemoteUri = new Uri("http://example.com"); - - var api = new BitbucketRestApi(context); - - var authMethods = await api.GetAuthenticationMethodsAsync(); - - httpHandler.AssertRequest(HttpMethod.Get, expectedRequestUri, 1); - - Assert.NotNull(authMethods); - Assert.Equal(authMethods.Count, impliedSupportedMethods.Count); - Assert.Contains(authMethods, m => impliedSupportedMethods.Contains(m)); - Assert.DoesNotContain(authMethods, m => impliedUnsupportedMethods.Contains(m)); - } - - public static IEnumerable GetAuthenticationMethodsAsyncData => - new List - { - new object[] { $"{{ \"results\":[ {{ \"type\":\"LOGIN_FORM\"}}]}}", - new List{AuthenticationMethod.BasicAuth}, - new List{AuthenticationMethod.Sso}}, - new object[] { $"{{ \"results\":[{{\"type\":\"IDP\"}}]}}", - new List{AuthenticationMethod.Sso}, - new List{AuthenticationMethod.BasicAuth}}, - new object[] { $"{{ \"results\":[{{\"type\":\"IDP\"}}, {{ \"type\":\"LOGIN_FORM\"}}, {{ \"type\":\"UNDEFINED\"}}]}}", - new List{AuthenticationMethod.Sso, AuthenticationMethod.BasicAuth}, - new List()}, - }; - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/LoginOptionsTest.cs b/src/shared/Atlassian.Bitbucket.Tests/DataCenter/LoginOptionsTest.cs deleted file mode 100644 index 6bb6efc85..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/LoginOptionsTest.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Atlassian.Bitbucket.DataCenter; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.DataCenter -{ - public class LoginOptionsTest - { - - [Fact] - public void LoginOptions_Set() - { - var loginOption = new LoginOption() - { - Type = "abc", - Id = 1 - }; - - var results = new List() - { - loginOption - }; - - var loginOptions = new LoginOptions() - { - Results = results - }; - - Assert.NotNull(loginOptions.Results); - Assert.Contains(loginOption, loginOptions.Results); - - Assert.Equal("abc", loginOptions.Results.First().Type); - Assert.Equal(1, loginOptions.Results.First().Id); - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/UserInfoTest.cs b/src/shared/Atlassian.Bitbucket.Tests/DataCenter/UserInfoTest.cs deleted file mode 100644 index 3090e0de3..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/DataCenter/UserInfoTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Threading.Tasks; -using Atlassian.Bitbucket.DataCenter; -using Xunit; - -namespace Atlassian.Bitbucket.Tests.DataCenter -{ - public class UserInfoTest - { - [Fact] - public void UserInfo_Set() - { - var userInfo = new UserInfo() - { - UserName = "123" - }; - - Assert.Equal("123", userInfo.UserName); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket.Tests/OAuth2ClientRegistryTest.cs b/src/shared/Atlassian.Bitbucket.Tests/OAuth2ClientRegistryTest.cs deleted file mode 100644 index c7bc06917..000000000 --- a/src/shared/Atlassian.Bitbucket.Tests/OAuth2ClientRegistryTest.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Atlassian.Bitbucket.Cloud; -using Atlassian.Bitbucket.DataCenter; -using GitCredentialManager; -using Moq; -using Xunit; - -namespace Atlassian.Bitbucket.Tests -{ - public class OAuth2ClientRegistryTest - { - private Mock context = new Mock(MockBehavior.Loose); - private Mock settings = new Mock(MockBehavior.Strict); - private Mock httpClientFactory = new Mock(MockBehavior.Strict); - private Mock trace = new Mock(MockBehavior.Strict); - - [Fact] - public void BitbucketRestApiRegistry_Get_ReturnsCloudOAuth2Client() - { - var host = "bitbucket.org"; - - // Given - settings.Setup(s => s.RemoteUri).Returns(new System.Uri("https://" + host)); - context.Setup(c => c.Settings).Returns(settings.Object); - MockSettingOverride(CloudConstants.EnvironmentVariables.OAuthClientId, CloudConstants.GitConfiguration.Credential.OAuthClientId, "never used", false); - MockSettingOverride(CloudConstants.EnvironmentVariables.OAuthClientSecret, CloudConstants.GitConfiguration.Credential.OAuthClientSecret, "never used", false); - MockSettingOverride(CloudConstants.EnvironmentVariables.OAuthRedirectUri, CloudConstants.GitConfiguration.Credential.OAuthRedirectUri, "never used", false); - MockHttpClientFactory(); - var input = MockInputArguments(host); - - // When - var registry = new OAuth2ClientRegistry(context.Object); - var api = registry.Get(input); - - // Then - Assert.NotNull(api); - Assert.IsType(api); - - } - - [Fact] - public void BitbucketRestApiRegistry_Get_ReturnsDataCenterOAuth2Client_ForBitbucketDC() - { - var host = "example.com"; - - // Given - settings.Setup(s => s.RemoteUri).Returns(new System.Uri("https://example.com")); - context.Setup(c => c.Settings).Returns(settings.Object); - MockSettingOverride(DataCenterConstants.EnvironmentVariables.OAuthClientId, DataCenterConstants.GitConfiguration.Credential.OAuthClientId, "", true); - MockSettingOverride(DataCenterConstants.EnvironmentVariables.OAuthClientSecret, DataCenterConstants.GitConfiguration.Credential.OAuthClientSecret, "", true); ; - MockSettingOverride(DataCenterConstants.EnvironmentVariables.OAuthRedirectUri, DataCenterConstants.GitConfiguration.Credential.OAuthRedirectUri, "never used", false); - MockHttpClientFactory(); - var input = MockInputArguments(host); - - // When - var registry = new OAuth2ClientRegistry(context.Object); - var api = registry.Get(input); - - // Then - Assert.NotNull(api); - Assert.IsType(api); - - } - - private static InputArguments MockInputArguments(string host) - { - return new InputArguments(new Dictionary - { - ["protocol"] = "https", - ["host"] = host, - }); - } - - private void MockHttpClientFactory() - { - context.Setup(c => c.HttpClientFactory).Returns(httpClientFactory.Object); - httpClientFactory.Setup(f => f.CreateClient()).Returns(new HttpClient()); - } - - private string MockSettingOverride(string envKey, string configKey, string settingValue, bool isOverridden) - { - settings.Setup(s => s.TryGetSetting( - envKey, - Constants.GitConfiguration.Credential.SectionName, configKey, - out settingValue)).Returns(isOverridden); - return settingValue; - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj b/src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj deleted file mode 100644 index 6aab348f8..000000000 --- a/src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - net8.0 - net8.0;net472 - Atlassian.Bitbucket - Atlassian.Bitbucket - false - latest - - - - - - - - - - - - - - - diff --git a/src/shared/Atlassian.Bitbucket/AuthenticationMethod.cs b/src/shared/Atlassian.Bitbucket/AuthenticationMethod.cs deleted file mode 100644 index 9e108c228..000000000 --- a/src/shared/Atlassian.Bitbucket/AuthenticationMethod.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -namespace Atlassian.Bitbucket -{ - public enum AuthenticationMethod - { - BasicAuth, - Sso - } -} - diff --git a/src/shared/Atlassian.Bitbucket/BitbucketAuthentication.cs b/src/shared/Atlassian.Bitbucket/BitbucketAuthentication.cs deleted file mode 100644 index def19970c..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketAuthentication.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Atlassian.Bitbucket.UI.ViewModels; -using Atlassian.Bitbucket.UI.Views; -using GitCredentialManager; -using GitCredentialManager.Authentication; -using GitCredentialManager.Authentication.OAuth; -using GitCredentialManager.UI; - -namespace Atlassian.Bitbucket -{ - - [Flags] - public enum AuthenticationModes - { - None = 0, - Basic = 1, - OAuth = 1 << 1, - - All = Basic | OAuth - } - public interface IBitbucketAuthentication : IDisposable - { - Task GetCredentialsAsync(Uri targetUri, string userName, AuthenticationModes modes); - Task CreateOAuthCredentialsAsync(InputArguments input); - Task RefreshOAuthCredentialsAsync(InputArguments input, string refreshToken); - string GetRefreshTokenServiceName(InputArguments input); - } - - public class CredentialsPromptResult - { - public CredentialsPromptResult(AuthenticationModes mode) - { - AuthenticationMode = mode; - } - - public CredentialsPromptResult(AuthenticationModes mode, ICredential credential) - : this(mode) - { - Credential = credential; - } - - public AuthenticationModes AuthenticationMode { get; } - - public ICredential Credential { get; set; } - } - - public class BitbucketAuthentication : AuthenticationBase, IBitbucketAuthentication - { - public static readonly string[] AuthorityIds = - { - BitbucketConstants.Id, - }; - - private readonly IRegistry _oauth2ClientRegistry; - - public BitbucketAuthentication(ICommandContext context) - : this(context, new OAuth2ClientRegistry(context)) { } - - public BitbucketAuthentication(ICommandContext context, IRegistry oauth2ClientRegistry) - : base(context) - { - EnsureArgument.NotNull(oauth2ClientRegistry, nameof(oauth2ClientRegistry)); - this._oauth2ClientRegistry = oauth2ClientRegistry; - } - - public async Task GetCredentialsAsync(Uri targetUri, string userName, AuthenticationModes modes) - { - ThrowIfUserInteractionDisabled(); - - // If we don't have a desktop session/GUI then we cannot offer OAuth since the only - // supported grant is authcode (i.e, using a web browser; device code is not supported). - if (!Context.SessionManager.IsDesktopSession) - { - modes = modes & ~AuthenticationModes.OAuth; - } - - // If the only supported mode is OAuth then just return immediately - if (modes == AuthenticationModes.OAuth) - { - return new CredentialsPromptResult(AuthenticationModes.OAuth); - } - - // We need at least one mode! - if (modes == AuthenticationModes.None) - { - throw new ArgumentException(@$"Must specify at least one {nameof(AuthenticationModes)}", nameof(modes)); - } - - // Shell out to the UI helper and show the Bitbucket u/p prompt - if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession) - { - if (TryFindHelperCommand(out string helperCommand, out string args)) - { - return await GetCredentialsViaHelperAsync(targetUri, userName, modes, helperCommand, args); - } - - return await GetCredentialsViaUiAsync(targetUri, userName, modes); - } - - return GetCredentialsViaTty(targetUri, userName, modes); - } - - private async Task GetCredentialsViaUiAsync( - Uri targetUri, string userName, AuthenticationModes modes) - { - var viewModel = new CredentialsViewModel(Context.Environment) - { - ShowOAuth = (modes & AuthenticationModes.OAuth) != 0, - ShowBasic = (modes & AuthenticationModes.Basic) != 0 - }; - - if (!BitbucketHelper.IsBitbucketOrg(targetUri)) - { - viewModel.Url = targetUri; - } - - if (!string.IsNullOrWhiteSpace(userName)) - { - viewModel.UserName = userName; - } - - await AvaloniaUi.ShowViewAsync(viewModel, GetParentWindowHandle(), CancellationToken.None); - - ThrowIfWindowCancelled(viewModel); - - switch (viewModel.SelectedMode) - { - case AuthenticationModes.OAuth: - return new CredentialsPromptResult(AuthenticationModes.OAuth); - - case AuthenticationModes.Basic: - return new CredentialsPromptResult( - AuthenticationModes.Basic, - new GitCredential(viewModel.UserName, viewModel.Password) - ); - - default: - throw new ArgumentOutOfRangeException(nameof(AuthenticationModes), - "Unknown authentication mode", viewModel.SelectedMode.ToString()); - } - } - - private CredentialsPromptResult GetCredentialsViaTty(Uri targetUri, string userName, AuthenticationModes modes) - { - ThrowIfTerminalPromptsDisabled(); - - switch (modes) - { - case AuthenticationModes.Basic: - Context.Terminal.WriteLine("Enter Bitbucket credentials for '{0}'...", targetUri); - - if (!string.IsNullOrWhiteSpace(userName)) - { - // Don't need to prompt for the username if it has been specified already - Context.Terminal.WriteLine("Username: {0}", userName); - } - else - { - // Prompt for username - userName = Context.Terminal.Prompt("Username"); - } - - // Prompt for password - string password = Context.Terminal.PromptSecret("Password"); - - return new CredentialsPromptResult( - AuthenticationModes.Basic, - new GitCredential(userName, password)); - - case AuthenticationModes.OAuth: - return new CredentialsPromptResult(AuthenticationModes.OAuth); - - case AuthenticationModes.None: - throw new ArgumentOutOfRangeException(nameof(modes), - @$"At least one {nameof(AuthenticationModes)} must be supplied"); - - default: - var menuTitle = $"Select an authentication method for '{targetUri}'"; - var menu = new TerminalMenu(Context.Terminal, menuTitle); - - TerminalMenuItem oauthItem = null; - TerminalMenuItem basicItem = null; - - if ((modes & AuthenticationModes.OAuth) != 0) oauthItem = menu.Add("OAuth"); - if ((modes & AuthenticationModes.Basic) != 0) basicItem = menu.Add("Username/password"); - - // Default to the 'first' choice in the menu - TerminalMenuItem choice = menu.Show(0); - - if (choice == oauthItem) goto case AuthenticationModes.OAuth; - if (choice == basicItem) goto case AuthenticationModes.Basic; - - throw new Exception(); - } - } - - private async Task GetCredentialsViaHelperAsync( - Uri targetUri, string userName, AuthenticationModes modes, string helperCommand, string args) - { - var promptArgs = new StringBuilder(args); - promptArgs.Append("prompt"); - if (!BitbucketHelper.IsBitbucketOrg(targetUri)) - { - promptArgs.AppendFormat(" --url {0}", QuoteCmdArg(targetUri.ToString())); - } - - if (!string.IsNullOrWhiteSpace(userName)) - { - promptArgs.AppendFormat(" --username {0}", QuoteCmdArg(userName)); - } - - if ((modes & AuthenticationModes.Basic) != 0) - { - promptArgs.Append(" --show-basic"); - } - - if ((modes & AuthenticationModes.OAuth) != 0) - { - promptArgs.Append(" --show-oauth"); - } - - IDictionary output = await InvokeHelperAsync(helperCommand, promptArgs.ToString()); - - if (output.TryGetValue("mode", out string mode) && - StringComparer.OrdinalIgnoreCase.Equals(mode, "oauth")) - { - return new CredentialsPromptResult(AuthenticationModes.OAuth); - } - else - { - if (!output.TryGetValue("username", out userName)) - { - throw new Trace2Exception(Context.Trace2, "Missing username in response"); - } - - if (!output.TryGetValue("password", out string password)) - { - throw new Trace2Exception(Context.Trace2, "Missing password in response"); - } - - return new CredentialsPromptResult( - AuthenticationModes.Basic, - new GitCredential(userName, password)); - } - } - - public async Task CreateOAuthCredentialsAsync(InputArguments input) - { - ThrowIfUserInteractionDisabled(); - - var browserOptions = new OAuth2WebBrowserOptions - { - SuccessResponseHtml = BitbucketResources.AuthenticationResponseSuccessHtml, - FailureResponseHtmlFormat = BitbucketResources.AuthenticationResponseFailureHtmlFormat - }; - - var browser = new OAuth2SystemWebBrowser(Context.Environment, browserOptions); - var oauth2Client = _oauth2ClientRegistry.Get(input); - - var authCodeResult = await oauth2Client.GetAuthorizationCodeAsync(browser, CancellationToken.None); - return await oauth2Client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None); - } - - public async Task RefreshOAuthCredentialsAsync(InputArguments input, string refreshToken) - { - var client = _oauth2ClientRegistry.Get(input); - return await client.GetTokenByRefreshTokenAsync(refreshToken, CancellationToken.None); - } - - public string GetRefreshTokenServiceName(InputArguments input) - { - var client = _oauth2ClientRegistry.Get(input); - return client.GetRefreshTokenServiceName(input); - } - - protected internal virtual bool TryFindHelperCommand(out string command, out string args) - { - return TryFindHelperCommand( - BitbucketConstants.EnvironmentVariables.AuthenticationHelper, - BitbucketConstants.GitConfiguration.Credential.AuthenticationHelper, - BitbucketConstants.DefaultAuthenticationHelper, - out command, - out args); - } - - private HttpClient _httpClient; - private HttpClient HttpClient => _httpClient ??= Context.HttpClientFactory.CreateClient(); - - public void Dispose() - { - _httpClient?.Dispose(); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/BitbucketConstants.cs b/src/shared/Atlassian.Bitbucket/BitbucketConstants.cs deleted file mode 100644 index 514f05ea4..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketConstants.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -namespace Atlassian.Bitbucket -{ - public static class BitbucketConstants - { - public const string Id = "bitbucket"; - - public const string Name = "Bitbucket"; - - public const string DefaultAuthenticationHelper = "Atlassian.Bitbucket.UI"; - - public static class EnvironmentVariables - { - public const string AuthenticationHelper = "GCM_BITBUCKET_HELPER"; - public const string AuthenticationModes = "GCM_BITBUCKET_AUTHMODES"; - public const string AlwaysRefreshCredentials = "GCM_BITBUCKET_ALWAYS_REFRESH_CREDENTIALS"; - public const String ValidateStoredCredentials = "GCM_BITBUCKET_VALIDATE_STORED_CREDENTIALS"; - } - - public static class GitConfiguration - { - public static class Credential - { - public const string AuthenticationHelper = "bitbucketHelper"; - public const string AuthenticationModes = "bitbucketAuthModes"; - public const string AlwaysRefreshCredentials = "bitbucketAlwaysRefreshCredentials"; - public const string ValidateStoredCredentials = "bitbucketValidateStoredCredentials"; - } - } - public static class HelpUrls - { - public const string DataCenterPasswordReset = "/passwordreset"; - public const string DataCenterLogin = "/login"; - public const string PasswordReset = "https://bitbucket.org/account/password/reset/"; - public const string SignUp = "https://bitbucket.org/account/signup/"; - public const string TwoFactor = "https://support.atlassian.com/bitbucket-cloud/docs/enable-two-step-verification/"; - } - - } -} diff --git a/src/shared/Atlassian.Bitbucket/BitbucketHelper.cs b/src/shared/Atlassian.Bitbucket/BitbucketHelper.cs deleted file mode 100644 index 3624627f0..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketHelper.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Atlassian.Bitbucket.Cloud; -using GitCredentialManager; - -namespace Atlassian.Bitbucket -{ - public static class BitbucketHelper - { - public static string GetBaseUri(Uri remoteUri) - { - var pathParts = remoteUri.PathAndQuery.Split('/'); - var pathPart = remoteUri.PathAndQuery.StartsWith("/") ? pathParts[1] : pathParts[0]; - var path = !string.IsNullOrWhiteSpace(pathPart) ? "/" + pathPart : null; - return $"{remoteUri.Scheme}://{remoteUri.Host}:{remoteUri.Port}{path}"; - } - - public static bool IsBitbucketOrg(InputArguments input) - { - return IsBitbucketOrg(input.GetRemoteUri()); - } - - public static bool IsBitbucketOrg(Uri targetUri) - { - return IsBitbucketOrg(targetUri.Host); - } - - public static bool IsBitbucketOrg(string targetHost) - { - return StringComparer.OrdinalIgnoreCase.Equals(targetHost, CloudConstants.BitbucketBaseUrlHost); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs b/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs deleted file mode 100644 index 286398de9..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketHostProvider.cs +++ /dev/null @@ -1,477 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Atlassian.Bitbucket.Cloud; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; - -namespace Atlassian.Bitbucket -{ - public class BitbucketHostProvider : IHostProvider - { - private readonly ICommandContext _context; - private readonly IBitbucketAuthentication _bitbucketAuth; - private readonly IRegistry _restApiRegistry; - - public BitbucketHostProvider(ICommandContext context) - : this(context, new BitbucketAuthentication(context), new BitbucketRestApiRegistry(context)) { } - - public BitbucketHostProvider(ICommandContext context, IBitbucketAuthentication bitbucketAuth, IRegistry restApiRegistry) - { - EnsureArgument.NotNull(context, nameof(context)); - EnsureArgument.NotNull(bitbucketAuth, nameof(bitbucketAuth)); - EnsureArgument.NotNull(restApiRegistry, nameof(restApiRegistry)); - - _context = context; - _bitbucketAuth = bitbucketAuth; - _restApiRegistry = restApiRegistry; - } - - #region IHostProvider - - public string Id => BitbucketConstants.Id; - - public string Name => BitbucketConstants.Name; - - public IEnumerable SupportedAuthorityIds => BitbucketAuthentication.AuthorityIds; - - public bool IsSupported(InputArguments input) - { - if (input is null) - { - return false; - } - - if (input.WwwAuth.Any(x => x.Contains("realm=\"Atlassian Bitbucket\"", StringComparison.InvariantCultureIgnoreCase))) - { - return true; - } - - // Split port number and hostname from host input argument - if (!input.TryGetHostAndPort(out string hostName, out _)) - { - return false; - } - - // We do not recommend unencrypted HTTP communications to Bitbucket, but it is possible. - // Therefore, we report `true` here for HTTP so that we can show a helpful - // error message for the user in `GetCredentialAsync`. - return (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") || - StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https")) && - hostName.EndsWith(CloudConstants.BitbucketBaseUrlHost, StringComparison.OrdinalIgnoreCase); - } - - public bool IsSupported(HttpResponseMessage response) - { - if (response is null) - { - return false; - } - - // Identify Bitbucket on-prem instances from the HTTP response using the Atlassian specific header X-AREQUESTID - var supported = response.Headers.Contains("X-AREQUESTID"); - - _context.Trace.WriteLine($"Host is{(supported ? null : "n't")} supported as Bitbucket"); - - return supported; - } - - public async Task GetCredentialAsync(InputArguments input) - { - // We should not allow unencrypted communication and should inform the user - if (!_context.Settings.AllowUnsafeRemotes && - StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") && - BitbucketHelper.IsBitbucketOrg(input)) - { - throw new Trace2Exception(_context.Trace2, - "Unencrypted HTTP is not recommended for Bitbucket.org. " + - "Ensure the repository remote URL is using HTTPS " + - $"or see {Constants.HelpUrls.GcmUnsafeRemotes} about how to allow unsafe remotes."); - } - - var authModes = await GetSupportedAuthenticationModesAsync(input); - - return await GetStoredCredentials(input, authModes) ?? - await GetRefreshedCredentials(input, authModes); - } - - private async Task GetStoredCredentials(InputArguments input, AuthenticationModes authModes) - { - if (_context.Settings.TryGetSetting(BitbucketConstants.EnvironmentVariables.AlwaysRefreshCredentials, - Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.AlwaysRefreshCredentials, - out string alwaysRefreshCredentials) && alwaysRefreshCredentials.ToBooleanyOrDefault(false)) - { - _context.Trace.WriteLine("Ignore stored credentials"); - return null; - } - - Uri remoteUri = input.GetRemoteUri(); - string credentialService = GetServiceName(remoteUri); - _context.Trace.WriteLine($"Look for existing credentials under {credentialService} ..."); - - ICredential credentials = _context.CredentialStore.Get(credentialService, input.UserName); - - if (credentials == null) - { - _context.Trace.WriteLine("No stored credentials found"); - return null; - } - - _context.Trace.WriteLineSecrets($"Found stored credentials: {credentials.Account}/{{0}}", new object[] { credentials.Password }); - - // Check credentials are still valid - if (!await ValidateCredentialsWork(input, credentials, authModes)) - { - return null; - } - - return credentials; - } - - private async Task GetRefreshedCredentials(InputArguments input, AuthenticationModes authModes) - { - _context.Trace.WriteLine("Refresh credentials..."); - - // Check for presence of refresh_token entry in credential store - Uri remoteUri = input.GetRemoteUri(); - var refreshTokenService = GetRefreshTokenServiceName(remoteUri); - - _context.Trace.WriteLine("Checking for refresh token..."); - ICredential refreshToken = SupportsOAuth(authModes) - ? _context.CredentialStore.Get(refreshTokenService, input.UserName) - : null; - - if (refreshToken is null) - { - _context.Trace.WriteLine("No stored refresh token found"); - // There is no refresh token either because this is a non-2FA enabled account (where OAuth is not - // required), or because we previously erased the RT. - - _context.Trace.WriteLine("Prompt for credentials..."); - - var result = await _bitbucketAuth.GetCredentialsAsync(remoteUri, input.UserName, authModes); - if (result is null || result.AuthenticationMode == AuthenticationModes.None) - { - var message = "User cancelled credential prompt"; - _context.Trace.WriteLine(message); - throw new Trace2Exception(_context.Trace2, message); - } - - switch (result.AuthenticationMode) - { - case AuthenticationModes.Basic: - // Return the valid credential - return result.Credential; - - case AuthenticationModes.OAuth: - // If the user wants to use OAuth fall through to interactive auth - break; - - default: - throw new ArgumentOutOfRangeException( - $"Unexpected {nameof(AuthenticationModes)} returned from prompt"); - } - - // Fall through to the start of the interactive OAuth authentication flow - } - else - { - _context.Trace.WriteLineSecrets("Found stored refresh token: {0}", new object[] { refreshToken }); - - try - { - return await GetOAuthCredentialsViaRefreshFlow(input, refreshToken); - } - catch (OAuth2Exception ex) - { - var message = "Failed to refresh existing OAuth credential using refresh token"; - _context.Trace.WriteLine(message); - _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); - - // We failed to refresh the AT using the RT; log the refresh failure and fall through to restart - // the OAuth authentication flow - } - } - - return await GetOAuthCredentialsViaInteractiveBrowserFlow(input); - } - - private async Task GetOAuthCredentialsViaRefreshFlow(InputArguments input, ICredential refreshToken) - { - Uri remoteUri = input.GetRemoteUri(); - - var refreshTokenService = GetRefreshTokenServiceName(remoteUri); - _context.Trace.WriteLine("Refreshing OAuth credentials using refresh token..."); - - OAuth2TokenResult refreshResult = await _bitbucketAuth.RefreshOAuthCredentialsAsync(input, refreshToken.Password); - - // Resolve the username - _context.Trace.WriteLine("Resolving username for refreshed OAuth credential..."); - string refreshUserName = await ResolveOAuthUserNameAsync(input, refreshResult.AccessToken); - _context.Trace.WriteLine($"Username for refreshed OAuth credential is '{refreshUserName}'"); - - // Store the refreshed RT - _context.Trace.WriteLine("Storing new refresh token..."); - _context.CredentialStore.AddOrUpdate(refreshTokenService, remoteUri.GetUserName(), refreshResult.RefreshToken); - - // Return new access token - return new GitCredential(refreshUserName, refreshResult.AccessToken); - } - - private async Task GetOAuthCredentialsViaInteractiveBrowserFlow(InputArguments input) - { - Uri remoteUri = input.GetRemoteUri(); - - var refreshTokenService = GetRefreshTokenServiceName(remoteUri); - - // We failed to use the refresh token either because it didn't exist, or because the refresh token is no - // longer valid. Either way we must now try authenticating using OAuth interactively. - - // Start OAuth authentication flow - _context.Trace.WriteLine("Starting OAuth authentication flow..."); - OAuth2TokenResult oauthResult = await _bitbucketAuth.CreateOAuthCredentialsAsync(input); - - // Resolve the username - _context.Trace.WriteLine("Resolving username for OAuth credential..."); - string newUserName = await ResolveOAuthUserNameAsync(input, oauthResult.AccessToken); - _context.Trace.WriteLine($"Username for OAuth credential is '{newUserName}'"); - - // Store the new RT - _context.Trace.WriteLine("Storing new refresh token..."); - _context.CredentialStore.AddOrUpdate(refreshTokenService, newUserName, oauthResult.RefreshToken); - _context.Trace.WriteLine("Refresh token was successfully stored."); - - // Return the new AT as the credential - return new GitCredential(newUserName, oauthResult.AccessToken); - } - - private static bool SupportsOAuth(AuthenticationModes authModes) - { - return (authModes & AuthenticationModes.OAuth) != 0; - } - - private static bool SupportsBasicAuth(AuthenticationModes authModes) - { - return (authModes & AuthenticationModes.Basic) != 0; - } - - public async Task GetSupportedAuthenticationModesAsync(InputArguments input) - { - // Check for an explicit override for supported authentication modes - if (_context.Settings.TryGetSetting( - BitbucketConstants.EnvironmentVariables.AuthenticationModes, - Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.AuthenticationModes, - out string authModesStr)) - { - if (Enum.TryParse(authModesStr, true, out AuthenticationModes authModes) && authModes != AuthenticationModes.None) - { - _context.Trace.WriteLine($"Supported authentication modes override present: {authModes}"); - return authModes; - } - else - { - _context.Trace.WriteLine($"Invalid value for supported authentication modes override setting: '{authModesStr}'"); - } - } - - // It isn't possible to detect what Bitbucket.org is expecting so return the predefined answers. - if (BitbucketHelper.IsBitbucketOrg(input)) - { - // Bitbucket should use Basic, OAuth or manual PAT based authentication only - _context.Trace.WriteLine($"{input.GetRemoteUri()} is bitbucket.org - authentication schemes: '{CloudConstants.DotOrgAuthenticationModes}'"); - return CloudConstants.DotOrgAuthenticationModes; - } - - // For Bitbucket DC/Server the supported modes can be detected - _context.Trace.WriteLine($"{input.GetRemoteUri()} is Bitbucket DC - checking for supported authentication schemes..."); - - try - { - var authenticationMethods = await _restApiRegistry.Get(input).GetAuthenticationMethodsAsync(); - - var modes = AuthenticationModes.None; - - if (authenticationMethods.Contains(AuthenticationMethod.BasicAuth)) - { - modes |= AuthenticationModes.Basic; - } - - var isOauthInstalled = await _restApiRegistry.Get(input).IsOAuthInstalledAsync(); - if (isOauthInstalled) - { - modes |= AuthenticationModes.OAuth; - } - - _context.Trace.WriteLine($"Bitbucket DC/Server instance supports authentication schemes: {modes}"); - return modes; - } - catch (Exception ex) - { - var format = "Failed to query '{0}' for supported authentication schemes."; - var message = string.Format(format, input.GetRemoteUri()); - - _context.Trace.WriteLine(message); - _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message, format); - - _context.Terminal.WriteLine($"warning: {message}"); - - // Fall-back to offering all modes so the user is never blocked from authenticating by at least one mode - return AuthenticationModes.All; - } - } - - public Task StoreCredentialAsync(InputArguments input) - { - // It doesn't matter if this is an OAuth access token, or the literal username & password - // because we store them the same way, against the same credential key in the store. - // The OAuth refresh token is already stored on the 'get' request. - Uri remoteUri = input.GetRemoteUri(); - string service = GetServiceName(remoteUri); - - _context.Trace.WriteLine("Storing credential..."); - _context.CredentialStore.AddOrUpdate(service, input.UserName, input.Password); - _context.Trace.WriteLine("Credential was successfully stored."); - - return Task.CompletedTask; - } - - public Task EraseCredentialAsync(InputArguments input) - { - // Erase the stored credential (which may be either the literal username & password, or - // the OAuth access token). We don't need to erase the OAuth refresh token because on the - // next 'get' request, if the RT is bad we will erase and reacquire a new one at that point. - Uri remoteUri = input.GetRemoteUri(); - string service = GetServiceName(remoteUri); - - _context.Trace.WriteLine("Erasing credential..."); - if (_context.CredentialStore.Remove(service, input.UserName)) - { - _context.Trace.WriteLine("Credential was successfully erased."); - } - else - { - _context.Trace.WriteLine("Credential was not erased."); - } - - return Task.CompletedTask; - } - - #endregion - - #region Private Methods - - private async Task ResolveOAuthUserNameAsync(InputArguments input, string accessToken) - { - RestApiResult result = await _restApiRegistry.Get(input).GetUserInformationAsync(null, accessToken, isBearerToken: true); - if (result.Succeeded) - { - return result.Response.UserName; - } - - throw new Trace2Exception(_context.Trace2, - $"Failed to resolve username. HTTP: {result.StatusCode}"); - } - - private async Task ResolveBasicAuthUserNameAsync(InputArguments input, string username, string password) - { - RestApiResult result = await _restApiRegistry.Get(input).GetUserInformationAsync(username, password, isBearerToken: false); - if (result.Succeeded) - { - return result.Response.UserName; - } - - throw new Trace2Exception(_context.Trace2, - $"Failed to resolve username. HTTP: {result.StatusCode}"); - } - - private async Task ValidateCredentialsWork(InputArguments input, ICredential credentials, AuthenticationModes authModes) - { - if (_context.Settings.TryGetSetting( - BitbucketConstants.EnvironmentVariables.ValidateStoredCredentials, - Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials, - out string validateStoredCredentials) && !validateStoredCredentials.ToBooleanyOrDefault(true)) - { - _context.Trace.WriteLine($"Skipping validation of stored credentials due to {BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials} = {validateStoredCredentials}"); - return true; - } - - if (credentials is null) - { - return false; - } - - // TODO: ideally we'd also check if the credentials have expired based on some local metadata - // (once/if we get such metadata storage), and return false if they have. - // This would be more efficient than having to make REST API calls to check. - Uri remoteUri = input.GetRemoteUri(); - _context.Trace.WriteLineSecrets($"Validate credentials ({credentials.Account}/{{0}}) are fresh for {remoteUri} ...", new object[] { credentials.Password }); - - // Bitbucket supports both OAuth + Basic Auth unless there is explicit GCM configuration. - // The credentials could be for either scheme therefore need to potentially test both possibilities. - if (SupportsOAuth(authModes)) - { - try - { - await ResolveOAuthUserNameAsync(input, credentials.Password); - _context.Trace.WriteLine("Validated existing credentials using OAuth"); - return true; - } - catch (Exception ex) - { - var message = "Failed to validate existing credentials using OAuth"; - _context.Trace.WriteLine(message); - _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); - } - } - - if (SupportsBasicAuth(authModes)) - { - try - { - await ResolveBasicAuthUserNameAsync(input, credentials.Account, credentials.Password); - _context.Trace.WriteLine("Validated existing credentials using BasicAuth"); - return true; - } - catch (Exception ex) - { - var message = "Failed to validate existing credentials using Basic Auth"; - _context.Trace.WriteLine(message); - _context.Trace.WriteException(ex); - _context.Trace2.WriteError(message); - return false; - } - } - - return true; - } - - private static string GetServiceName(Uri remoteUri) - { - return remoteUri.WithoutUserInfo().AbsoluteUri.TrimEnd('/'); - } - - internal /* for testing */ static string GetRefreshTokenServiceName(Uri remoteUri) - { - Uri baseUri = remoteUri.WithoutUserInfo(); - - // The refresh token key never includes the path component. - // Instead we use the path component to specify this is the "refresh_token". - Uri uri = new UriBuilder(baseUri) { Path = "/refresh_token" }.Uri; - - return uri.AbsoluteUri.TrimEnd('/'); - } - - #endregion - - public void Dispose() - { - _restApiRegistry.Dispose(); - _bitbucketAuth.Dispose(); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs b/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs deleted file mode 100644 index 1ca23d0f5..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; - -namespace Atlassian.Bitbucket -{ - public abstract class BitbucketOAuth2Client : OAuth2Client - { - public BitbucketOAuth2Client(HttpClient httpClient, - OAuth2ServerEndpoints endpoints, - string clientId, - Uri redirectUri, - string clientSecret, - ITrace2 trace2) : base(httpClient, endpoints, clientId, trace2, redirectUri, clientSecret, false) - { - } - - public abstract IEnumerable Scopes { get; } - - public string GetRefreshTokenServiceName(InputArguments input) - { - Uri baseUri = input.GetRemoteUri(includeUser: false); - - // The refresh token key never includes the path component. - // Instead we use the path component to specify this is the "refresh_token". - Uri uri = new UriBuilder(baseUri) { Path = "/refresh_token" }.Uri; - - return uri.AbsoluteUri.TrimEnd('/'); - } - - public Task GetAuthorizationCodeAsync(IOAuth2WebBrowser browser, CancellationToken ct) - { - return this.GetAuthorizationCodeAsync(Scopes, browser, ct); - } - - protected override bool TryCreateTokenEndpointResult(string json, out OAuth2TokenResult result) - { - // We override the token endpoint response parsing because the Bitbucket authority returns - // the non-standard 'scopes' property for the list of scopes, rather than the (optional) - // 'scope' (note the singular vs plural) property as outlined in the standard. - if (TryDeserializeJson(json, out BitbucketTokenEndpointResponseJson jsonObj)) - { - result = jsonObj.ToResult(); - return true; - } - - result = null; - return false; - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/BitbucketResources.Designer.cs b/src/shared/Atlassian.Bitbucket/BitbucketResources.Designer.cs deleted file mode 100644 index 128a4e2b3..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketResources.Designer.cs +++ /dev/null @@ -1,61 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Atlassian.Bitbucket { - using System; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class BitbucketResources { - - private static System.Resources.ResourceManager resourceMan; - - private static System.Globalization.CultureInfo resourceCulture; - - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal BitbucketResources() { - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Atlassian.Bitbucket.BitbucketResources", typeof(BitbucketResources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - internal static string AuthenticationResponseSuccessHtml { - get { - return ResourceManager.GetString("AuthenticationResponseSuccessHtml", resourceCulture); - } - } - - internal static string AuthenticationResponseFailureHtmlFormat { - get { - return ResourceManager.GetString("AuthenticationResponseFailureHtmlFormat", resourceCulture); - } - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/BitbucketResources.resx b/src/shared/Atlassian.Bitbucket/BitbucketResources.resx deleted file mode 100644 index d7e6058e8..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketResources.resx +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - - Bitbucket Authentication - - - - - -
- Bitbucket -
-
-
-

Authentication Successful

-

Git Credential Manager has been successfully authenticated. You may now close this page.

-
-
-
- -
- -]]>
- - - - - Bitbucket Authentication - - - - - - -
- Bitbucket -
-
-
-
-

Authentication Failed

-

Git Credential Manager failed to be authenticated.

-
-
Error
-
{0} -
Description
-
{1} -
URL
-
{2} -
-
-
-
-
- -
- -]]>
-
diff --git a/src/shared/Atlassian.Bitbucket/BitbucketRestApiRegistry.cs b/src/shared/Atlassian.Bitbucket/BitbucketRestApiRegistry.cs deleted file mode 100644 index 950a46855..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketRestApiRegistry.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Atlassian.Bitbucket.Cloud; -using GitCredentialManager; - -namespace Atlassian.Bitbucket -{ - public class BitbucketRestApiRegistry : IRegistry - { - private readonly ICommandContext context; - private BitbucketRestApi cloudApi; - private DataCenter.BitbucketRestApi dataCenterApi; - - public BitbucketRestApiRegistry(ICommandContext context) - { - this.context = context; - } - - public IBitbucketRestApi Get(InputArguments input) - { - if(!BitbucketHelper.IsBitbucketOrg(input)) - { - return DataCenterApi; - } - - return CloudApi; - } - - public void Dispose() - { - context.Dispose(); - cloudApi?.Dispose(); - dataCenterApi?.Dispose(); - } - - private Cloud.BitbucketRestApi CloudApi => cloudApi ??= new Cloud.BitbucketRestApi(context); - private DataCenter.BitbucketRestApi DataCenterApi => dataCenterApi ??= new DataCenter.BitbucketRestApi(context); - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs b/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs deleted file mode 100644 index c28e63de1..000000000 --- a/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Text.Json; -using GitCredentialManager.Authentication.OAuth.Json; -using System.Text.Json.Serialization; - -namespace Atlassian.Bitbucket -{ - [JsonConverter(typeof(BitbucketCustomTokenEndpointResponseJsonConverter))] - public class BitbucketTokenEndpointResponseJson : TokenEndpointResponseJson - { - // To ensure the "scopes" property used by Bitbucket is deserialized successfully with System.Text.Json, we must - // use a custom converter. Otherwise, ordering will matter (i.e. if "scopes" is the final property, its value - // will be used, but if "scope" is the final property, its value will be used). - } - - public class BitbucketCustomTokenEndpointResponseJsonConverter : JsonConverter - { - public override BitbucketTokenEndpointResponseJson Read( - ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.StartObject) - { - throw new JsonException(); - } - - var response = new BitbucketTokenEndpointResponseJson(); - - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.EndObject) - { - return response; - } - - if (reader.TokenType == JsonTokenType.PropertyName) - { - var propertyName = reader.GetString(); - reader.Read(); - if (propertyName != null) - { - switch (propertyName) - { - case "access_token": - response.AccessToken = reader.GetString(); - break; - case "token_type": - response.TokenType = reader.GetString(); - break; - case "expires_in": - if (reader.TryGetUInt32(out var expiration)) - response.ExpiresIn = expiration; - else - response.ExpiresIn = null; - break; - case "refresh_token": - response.RefreshToken = reader.GetString(); - break; - case "scopes": - response.Scope = reader.GetString(); - break; - } - } - } - } - - throw new JsonException(); - } - - public override void Write( - Utf8JsonWriter writer, BitbucketTokenEndpointResponseJson tokenEndpointResponseJson, JsonSerializerOptions options) - { } - } -} diff --git a/src/shared/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs b/src/shared/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs deleted file mode 100644 index 4b5edbbf7..000000000 --- a/src/shared/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Net.Http; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; - -namespace Atlassian.Bitbucket.Cloud -{ - public class BitbucketOAuth2Client : Bitbucket.BitbucketOAuth2Client - { - public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2 trace2) - : base(httpClient, GetEndpoints(), - GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings), trace2) - { - } - - public override IEnumerable Scopes => new string[] { - CloudConstants.OAuthScopes.RepositoryWrite, - CloudConstants.OAuthScopes.Account, - }; - - private static string GetClientId(ISettings settings) - { - // Check for developer override value - if (settings.TryGetSetting( - CloudConstants.EnvironmentVariables.OAuthClientId, - Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientId, - out string clientId)) - { - return clientId; - } - - return CloudConstants.OAuth2ClientId; - } - - private static Uri GetRedirectUri(ISettings settings) - { - // Check for developer override value - if (settings.TryGetSetting( - CloudConstants.EnvironmentVariables.OAuthRedirectUri, - Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthRedirectUri, - out string redirectUriStr) && Uri.TryCreate(redirectUriStr, UriKind.Absolute, out Uri redirectUri)) - { - return redirectUri; - } - - return CloudConstants.OAuth2RedirectUri; - } - - private static string GetClientSecret(ISettings settings) - { - // Check for developer override value - if (settings.TryGetSetting( - CloudConstants.EnvironmentVariables.OAuthClientSecret, - Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientSecret, - out string clientSecret)) - { - return clientSecret; - } - - return CloudConstants.OAuth2ClientSecret; - } - - private static OAuth2ServerEndpoints GetEndpoints() - { - return new OAuth2ServerEndpoints( - CloudConstants.OAuth2AuthorizationEndpoint, - CloudConstants.OAuth2TokenEndpoint - ); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs b/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs deleted file mode 100644 index 94021e14d..000000000 --- a/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using GitCredentialManager; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Atlassian.Bitbucket.Cloud -{ - public class BitbucketRestApi : IBitbucketRestApi - { - private readonly ICommandContext _context; - private readonly Uri _apiUri = CloudConstants.BitbucketApiUri; - - public BitbucketRestApi(ICommandContext context) - { - EnsureArgument.NotNull(context, nameof(context)); - - _context = context; - } - - public async Task> GetUserInformationAsync(string userName, string password, bool isBearerToken) - { - var requestUri = new Uri(_apiUri, "2.0/user"); - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) - { - if (isBearerToken) - { - request.AddBearerAuthenticationHeader(password); - } - else - { - request.AddBasicAuthenticationHeader(userName, password); - } - - _context.Trace.WriteLine($"HTTP: GET {requestUri}"); - using (HttpResponseMessage response = await HttpClient.SendAsync(request)) - { - _context.Trace.WriteLine($"HTTP: Response {(int) response.StatusCode} [{response.StatusCode}]"); - - string json = await response.Content.ReadAsStringAsync(); - - if (response.IsSuccessStatusCode) - { - var obj = JsonSerializer.Deserialize(json, - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - }); - - return new RestApiResult(response.StatusCode, obj); - } - - return new RestApiResult(response.StatusCode); - } - } - } - - public Task IsOAuthInstalledAsync() - { - return Task.FromResult(true); - } - - public Task> GetAuthenticationMethodsAsync() - { - // For Bitbucket Cloud there is no REST API to determine login methods - // instead this is determined later in the process by attempting - // authenticated REST API requests and checking the response. - return Task.FromResult(new List()); - } - - private HttpClient _httpClient; - private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); - - public void Dispose() - { - _httpClient?.Dispose(); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/Cloud/CloudConstants.cs b/src/shared/Atlassian.Bitbucket/Cloud/CloudConstants.cs deleted file mode 100644 index 574435872..000000000 --- a/src/shared/Atlassian.Bitbucket/Cloud/CloudConstants.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace Atlassian.Bitbucket.Cloud -{ - public static class CloudConstants - { - public const string BitbucketBaseUrlHost = "bitbucket.org"; - public static readonly Uri BitbucketApiUri = new Uri("https://api.bitbucket.org"); - - // TODO: use the GCM client ID and secret once we have this approved. - // Until then continue to use Sourcetree's values like GCM Windows. - //public const string OAuth2ClientId = "b5AKdPfpgFdEGpKzPE"; - //public const string OAuth2ClientSecret = "7NUP5qUtSR3SxdFK4xAGaU6PMNvNdE59"; - //public static readonly Uri OAuth2RedirectUri = new Uri("http://localhost:46337/"); - public const string OAuth2ClientId = "HJdmKXV87DsmC9zSWB"; - public const string OAuth2ClientSecret = "wwWw47VB9ZHwMsD4Q4rAveHkbxNrMp3n"; - public static readonly Uri OAuth2RedirectUri = new Uri("http://localhost:34106/"); - - public static readonly Uri OAuth2AuthorizationEndpoint = new Uri("https://bitbucket.org/site/oauth2/authorize"); - public static readonly Uri OAuth2TokenEndpoint = new Uri("https://bitbucket.org/site/oauth2/access_token"); - - public static class OAuthScopes - { - public const string RepositoryWrite = "repository:write"; - public const string Account = "account"; - } - - /// - /// Supported authentication modes for Bitbucket.org - /// - public const AuthenticationModes DotOrgAuthenticationModes = AuthenticationModes.Basic | AuthenticationModes.OAuth; - - public static class EnvironmentVariables - { - public const string OAuthClientId = "GCM_BITBUCKET_CLOUD_CLIENTID"; - public const string OAuthClientSecret = "GCM_BITBUCKET_CLOUD_CLIENTSECRET"; - public const string OAuthRedirectUri = "GCM_BITBUCKET_CLOUD_OAUTH_REDIRECTURI"; - } - - public static class GitConfiguration - { - public static class Credential - { - public const string OAuthClientId = "cloudOAuthClientId"; - public const string OAuthClientSecret = "cloudOAuthClientSecret"; - public const string OAuthRedirectUri = "cloudOauthRedirectUri"; - } - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/Cloud/UserInfo.cs b/src/shared/Atlassian.Bitbucket/Cloud/UserInfo.cs deleted file mode 100644 index 9e74cbc2d..000000000 --- a/src/shared/Atlassian.Bitbucket/Cloud/UserInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Atlassian.Bitbucket.Cloud -{ - public class UserInfo : IUserInfo - { - [JsonPropertyName("username")] - public string UserName { get; set; } - } -} diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs b/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs deleted file mode 100644 index 97abd533c..000000000 --- a/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using GitCredentialManager; -using GitCredentialManager.Authentication.OAuth; - -namespace Atlassian.Bitbucket.DataCenter -{ - public class BitbucketOAuth2Client : Bitbucket.BitbucketOAuth2Client - { - public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2 trace2) - : base(httpClient, GetEndpoints(settings), - GetClientId(settings), GetRedirectUri(settings), GetClientSecret(settings), trace2) - { - } - - public override IEnumerable Scopes => new string[] { - DataCenterConstants.OAuthScopes.PublicRepos, - DataCenterConstants.OAuthScopes.RepoRead, - DataCenterConstants.OAuthScopes.RepoWrite - }; - - private static string GetClientId(ISettings settings) - { - // Check for developer override value - if (settings.TryGetSetting( - DataCenterConstants.EnvironmentVariables.OAuthClientId, - Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientId, - out string clientId)) - { - return clientId; - } - - throw new ArgumentException("Bitbucket DC OAuth Client ID must be defined"); - } - - private static Uri GetRedirectUri(ISettings settings) - { - // Check for developer override value - if (settings.TryGetSetting( - DataCenterConstants.EnvironmentVariables.OAuthRedirectUri, - Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthRedirectUri, - out string redirectUriStr) && Uri.TryCreate(redirectUriStr, UriKind.Absolute, out Uri redirectUri)) - { - return redirectUri; - } - - return DataCenterConstants.OAuth2RedirectUri; - } - - private static string GetClientSecret(ISettings settings) - { - // Check for developer override value - if (settings.TryGetSetting( - DataCenterConstants.EnvironmentVariables.OAuthClientSecret, - Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientSecret, - out string clientSecret)) - { - return clientSecret; - } - - throw new ArgumentException("Bitbucket DC OAuth Client Secret must be defined"); - } - - private static OAuth2ServerEndpoints GetEndpoints(ISettings settings) - { - var remoteUri = settings.RemoteUri; - if (remoteUri == null) - { - throw new ArgumentException("RemoteUri must be defined to generate Bitbucket DC OAuth2 endpoint Urls"); - } - - return new OAuth2ServerEndpoints( - new Uri(BitbucketHelper.GetBaseUri(remoteUri) + "/rest/oauth2/latest/authorize"), - new Uri(BitbucketHelper.GetBaseUri(remoteUri) + "/rest/oauth2/latest/token") - ); - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs b/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs deleted file mode 100644 index 159229885..000000000 --- a/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using GitCredentialManager; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Atlassian.Bitbucket.DataCenter -{ - public class BitbucketRestApi : IBitbucketRestApi - { - private readonly ICommandContext _context; - - private HttpClient _httpClient; - - public BitbucketRestApi(ICommandContext context) - { - EnsureArgument.NotNull(context, nameof(context)); - - _context = context; - - } - - public async Task> GetUserInformationAsync(string userName, string password, bool isBearerToken) - { - if (_context.Settings.TryGetSetting( - BitbucketConstants.EnvironmentVariables.ValidateStoredCredentials, - Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials, - out string validateStoredCredentials) && !validateStoredCredentials.ToBooleanyOrDefault(true)) - { - _context.Trace.WriteLine($"Skipping retreival of user information due to {BitbucketConstants.GitConfiguration.Credential.ValidateStoredCredentials} = {validateStoredCredentials}"); - return new RestApiResult(HttpStatusCode.OK, new UserInfo() { UserName = DataCenterConstants.OAuthUserName });; - } - - // Bitbucket Server/DC doesn't actually provide a REST API we can use to trade an access_token for the owning username, - // therefore this is always going to return a placeholder username, however this call does provide a way to validate the - // credentials we do have - var requestUri = new Uri(ApiUri, "api/1.0/users"); - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) - { - if (isBearerToken) - { - request.AddBearerAuthenticationHeader(password); - } - else - { - request.AddBasicAuthenticationHeader(userName, password); - } - - _context.Trace.WriteLine($"HTTP: GET {requestUri}"); - using (HttpResponseMessage response = await HttpClient.SendAsync(request)) - { - _context.Trace.WriteLine($"HTTP: Response {(int) response.StatusCode} [{response.StatusCode}]"); - - string json = await response.Content.ReadAsStringAsync(); - - if (response.IsSuccessStatusCode) - { - // No REST API in BBS that can be used to return just my user account based on my login AFAIK. - // but we can prove the credentials work. - return new RestApiResult(HttpStatusCode.OK, new UserInfo() { UserName = DataCenterConstants.OAuthUserName }); - } - - return new RestApiResult(response.StatusCode); - } - } - - } - - public async Task IsOAuthInstalledAsync() - { - var requestUri = new Uri(ApiUri.AbsoluteUri + "oauth2/1.0/client"); - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) - { - _context.Trace.WriteLine($"HTTP: GET {requestUri}"); - using (HttpResponseMessage response = await HttpClient.SendAsync(request)) - { - _context.Trace.WriteLine($"HTTP: Response {(int)response.StatusCode} [{response.StatusCode}]"); - - if (HttpStatusCode.Unauthorized == response.StatusCode) - { - // accessed anonymously so no access but it does exist. - return true; - } - - return false; - } - } - } - - public async Task> GetAuthenticationMethodsAsync() - { - var authenticationMethods = new List(); - - var requestUri = new Uri(ApiUri.AbsoluteUri + "authconfig/1.0/login-options"); - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri)) - { - _context.Trace.WriteLine($"HTTP: GET {requestUri}"); - using (HttpResponseMessage response = await HttpClient.SendAsync(request)) - { - _context.Trace.WriteLine($"HTTP: Response {(int)response.StatusCode} [{response.StatusCode}]"); - - string json = await response.Content.ReadAsStringAsync(); - - if (response.IsSuccessStatusCode) - { - var loginOptions = JsonSerializer.Deserialize(json, - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }); - - if (loginOptions.Results.Any(r => "LOGIN_FORM".Equals(r.Type))) - { - authenticationMethods.Add(AuthenticationMethod.BasicAuth); - } - - if (loginOptions.Results.Any(r => "IDP".Equals(r.Type))) - { - authenticationMethods.Add(AuthenticationMethod.Sso); - } - } - } - } - - return authenticationMethods; - } - - public void Dispose() - { - _httpClient?.Dispose(); - } - - private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); - - private Uri ApiUri - { - get - { - var remoteUri = _context.Settings?.RemoteUri; - if (remoteUri == null) - { - throw new ArgumentException("RemoteUri must be defined to generate Bitbucket DC OAuth2 endpoint Urls"); - } - - return new Uri(BitbucketHelper.GetBaseUri(remoteUri) + "/rest/"); - } - } - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/DataCenterConstants.cs b/src/shared/Atlassian.Bitbucket/DataCenter/DataCenterConstants.cs deleted file mode 100644 index 526db40c0..000000000 --- a/src/shared/Atlassian.Bitbucket/DataCenter/DataCenterConstants.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; - -namespace Atlassian.Bitbucket.DataCenter -{ - public static class DataCenterConstants - { - public static class OAuthScopes - { - public const string PublicRepos = "PUBLIC_REPOS"; - public const string RepoWrite = "REPO_WRITE"; - public const string RepoRead = "REPO_READ"; - } - - public static readonly Uri OAuth2RedirectUri = new Uri("http://localhost:34106/"); - - /// - /// Supported authentication modes for Bitbucket Server/DC - /// - public const AuthenticationModes ServerAuthenticationModes = AuthenticationModes.Basic | AuthenticationModes.OAuth; - - /// - /// Bitbucket Server/DC does not have a REST API we can use to trade an OAuth access_token for the owning username. - /// However one is needed to construct the Basic Auth request made by Git HTTP requests, therefore use a hardcoded - /// placeholder for the username. - /// - public const string OAuthUserName = "OAUTH_USERNAME"; - - public static class EnvironmentVariables - { - public const string OAuthClientId = "GCM_BITBUCKET_DATACENTER_CLIENTID"; - public const string OAuthClientSecret = "GCM_BITBUCKET_DATACENTER_CLIENTSECRET"; - public const string OAuthRedirectUri = "GCM_BITBUCKET_DATACENTER_OAUTH_REDIRECTURI"; - } - - public static class GitConfiguration - { - public static class Credential - { - public const string OAuthClientId = "bitbucketDataCenterOAuthClientId"; - public const string OAuthClientSecret = "bitbucketDataCenterOAuthClientSecret"; - public const string OAuthRedirectUri = "bitbucketDataCenterOauthRedirectUri"; - } - } - } -} diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/LoginOption.cs b/src/shared/Atlassian.Bitbucket/DataCenter/LoginOption.cs deleted file mode 100644 index a9bfb6bd4..000000000 --- a/src/shared/Atlassian.Bitbucket/DataCenter/LoginOption.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Atlassian.Bitbucket.DataCenter -{ - public class LoginOption - { - [JsonPropertyName("type")] - public string Type { get ; set; } - - [JsonPropertyName("id")] - public int Id { get; set; } - } -} diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/LoginOptions.cs b/src/shared/Atlassian.Bitbucket/DataCenter/LoginOptions.cs deleted file mode 100644 index ddc0f8509..000000000 --- a/src/shared/Atlassian.Bitbucket/DataCenter/LoginOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Atlassian.Bitbucket.DataCenter -{ - public class LoginOptions - { - [JsonPropertyName("results")] - public List Results { get; set; } - } -} diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/UserInfo.cs b/src/shared/Atlassian.Bitbucket/DataCenter/UserInfo.cs deleted file mode 100644 index 2b0b28730..000000000 --- a/src/shared/Atlassian.Bitbucket/DataCenter/UserInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Atlassian.Bitbucket.DataCenter -{ - public class UserInfo : IUserInfo - { - public string UserName { get; set; } - } -} diff --git a/src/shared/Atlassian.Bitbucket/IBitbucketRestApi.cs b/src/shared/Atlassian.Bitbucket/IBitbucketRestApi.cs deleted file mode 100644 index d89d174b2..000000000 --- a/src/shared/Atlassian.Bitbucket/IBitbucketRestApi.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Atlassian.Bitbucket -{ - public interface IBitbucketRestApi : IDisposable - { - Task> GetUserInformationAsync(string userName, string password, bool isBearerToken); - Task IsOAuthInstalledAsync(); - Task> GetAuthenticationMethodsAsync(); - } -} diff --git a/src/shared/Atlassian.Bitbucket/IRegistry.cs b/src/shared/Atlassian.Bitbucket/IRegistry.cs deleted file mode 100644 index 5712e3770..000000000 --- a/src/shared/Atlassian.Bitbucket/IRegistry.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using GitCredentialManager; - -namespace Atlassian.Bitbucket -{ - public interface IRegistry : IDisposable - { - T Get(InputArguments input); - } -} \ No newline at end of file diff --git a/src/shared/Atlassian.Bitbucket/IUserInfo.cs b/src/shared/Atlassian.Bitbucket/IUserInfo.cs deleted file mode 100644 index 6ec01b81f..000000000 --- a/src/shared/Atlassian.Bitbucket/IUserInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; -namespace Atlassian.Bitbucket -{ - public interface IUserInfo - { - string UserName{ get; } - } -} diff --git a/src/shared/Atlassian.Bitbucket/InternalsVisibleTo.cs b/src/shared/Atlassian.Bitbucket/InternalsVisibleTo.cs deleted file mode 100644 index 11a8ae4a8..000000000 --- a/src/shared/Atlassian.Bitbucket/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Atlassian.Bitbucket.Tests")] diff --git a/src/shared/Atlassian.Bitbucket/OAuth2ClientRegistry.cs b/src/shared/Atlassian.Bitbucket/OAuth2ClientRegistry.cs deleted file mode 100644 index 800617ce8..000000000 --- a/src/shared/Atlassian.Bitbucket/OAuth2ClientRegistry.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Net.Http; -using GitCredentialManager; - -namespace Atlassian.Bitbucket -{ - public class OAuth2ClientRegistry : DisposableObject, IRegistry - { - private readonly ICommandContext _context; - - private HttpClient _httpClient; - private Cloud.BitbucketOAuth2Client _cloudClient; - private DataCenter.BitbucketOAuth2Client _dataCenterClient; - - public OAuth2ClientRegistry(ICommandContext context) - { - EnsureArgument.NotNull(context, nameof(context)); - _context = context; - } - - public BitbucketOAuth2Client Get(InputArguments input) - { - if (!BitbucketHelper.IsBitbucketOrg(input)) - { - return DataCenterClient; - } - - return CloudClient; - } - - protected override void ReleaseManagedResources() - { - _httpClient?.Dispose(); - _cloudClient = null; - _dataCenterClient = null; - base.ReleaseManagedResources(); - } - - private HttpClient HttpClient => _httpClient ??= _context.HttpClientFactory.CreateClient(); - - private Cloud.BitbucketOAuth2Client CloudClient => - _cloudClient ??= new Cloud.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); - - private DataCenter.BitbucketOAuth2Client DataCenterClient => - _dataCenterClient ??= new DataCenter.BitbucketOAuth2Client(HttpClient, _context.Settings, _context.Trace2); - } -} diff --git a/src/shared/Atlassian.Bitbucket/RestApiResult.cs b/src/shared/Atlassian.Bitbucket/RestApiResult.cs deleted file mode 100644 index 63c50cfdd..000000000 --- a/src/shared/Atlassian.Bitbucket/RestApiResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Net; - -namespace Atlassian.Bitbucket -{ - public class RestApiResult - { - public RestApiResult(HttpStatusCode statusCode) - : this(statusCode, default(T)) { } - - public RestApiResult(HttpStatusCode statusCode, T response) - { - StatusCode = statusCode; - Response = response; - } - - public HttpStatusCode StatusCode { get; } - - public T Response { get; } - - public bool Succeeded => 199 < (int)StatusCode && (int)StatusCode < 300; - } -} diff --git a/src/shared/Atlassian.Bitbucket/UI/Assets/atlassian-logo.png b/src/shared/Atlassian.Bitbucket/UI/Assets/atlassian-logo.png deleted file mode 100644 index 6226f936d..000000000 Binary files a/src/shared/Atlassian.Bitbucket/UI/Assets/atlassian-logo.png and /dev/null differ diff --git a/src/shared/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs b/src/shared/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs deleted file mode 100644 index 72c20f513..000000000 --- a/src/shared/Atlassian.Bitbucket/UI/Commands/CredentialsCommand.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.CommandLine.Invocation; -using System.Threading; -using System.Threading.Tasks; -using Atlassian.Bitbucket.UI.ViewModels; -using GitCredentialManager; -using GitCredentialManager.UI; - -namespace Atlassian.Bitbucket.UI.Commands -{ - public abstract class CredentialsCommand : HelperCommand - { - protected CredentialsCommand(ICommandContext context) - : base(context, "prompt", "Show authentication prompt.") - { - var url = new Option("--url", "Bitbucket Server or Data Center URL"); - AddOption(url); - - var userName = new Option("--username", "Username or email."); - AddOption(userName); - - var oauth = new Option("--show-oauth", "Show OAuth option."); - AddOption(oauth); - - var basic = new Option("--show-basic", "Show username/password option."); - AddOption(basic); - - this.SetHandler(ExecuteAsync, url, userName, oauth, basic); - } - - private async Task ExecuteAsync(Uri url, string userName, bool showOAuth, bool showBasic) - { - var viewModel = new CredentialsViewModel(Context.Environment) - { - Url = url, - UserName = userName, - ShowOAuth = showOAuth, - ShowBasic = showBasic - }; - - await ShowAsync(viewModel, CancellationToken.None); - - if (!viewModel.WindowResult || viewModel.SelectedMode == AuthenticationModes.None) - { - throw new Trace2Exception(Context.Trace2, "User cancelled dialog."); - } - - switch (viewModel.SelectedMode) - { - case AuthenticationModes.OAuth: - WriteResult(new Dictionary - { - ["mode"] = "oauth" - }); - break; - - case AuthenticationModes.Basic: - WriteResult(new Dictionary - { - ["mode"] = "basic", - ["username"] = viewModel.UserName, - ["password"] = viewModel.Password, - }); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(AuthenticationModes), - "Unknown authentication mode", viewModel.SelectedMode.ToString()); - } - - return 0; - } - - protected abstract Task ShowAsync(CredentialsViewModel viewModel, CancellationToken ct); - } -} diff --git a/src/shared/Atlassian.Bitbucket/UI/ViewModels/CredentialsViewModel.cs b/src/shared/Atlassian.Bitbucket/UI/ViewModels/CredentialsViewModel.cs deleted file mode 100644 index d4e8e51c9..000000000 --- a/src/shared/Atlassian.Bitbucket/UI/ViewModels/CredentialsViewModel.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.ComponentModel; -using System.Windows.Input; -using GitCredentialManager; -using GitCredentialManager.UI; -using GitCredentialManager.UI.ViewModels; - -namespace Atlassian.Bitbucket.UI.ViewModels -{ - public class CredentialsViewModel : WindowViewModel - { - private readonly IEnvironment _environment; - - private Uri _url; - private string _userName; - private string _password; - private bool _showOAuth; - private bool _showBasic; - - public CredentialsViewModel() - { - // Constructor the XAML designer - } - - public CredentialsViewModel(IEnvironment environment) - { - EnsureArgument.NotNull(environment, nameof(environment)); - - _environment = environment; - - Title = "Connect to Bitbucket"; - LoginCommand = new RelayCommand(AcceptBasic, CanLogin); - CancelCommand = new RelayCommand(Cancel); - OAuthCommand = new RelayCommand(AcceptOAuth, CanAcceptOAuth); - ForgotPasswordCommand = new RelayCommand(ForgotPassword); - SignUpCommand = new RelayCommand(SignUp); - - PropertyChanged += OnPropertyChanged; - } - - private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case nameof(UserName): - case nameof(Password): - LoginCommand.RaiseCanExecuteChanged(); - break; - } - } - - private bool CanLogin() - { - return !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password); - } - - private void AcceptBasic() - { - SelectedMode = AuthenticationModes.Basic; - Accept(); - } - - private void AcceptOAuth() - { - SelectedMode = AuthenticationModes.OAuth; - Accept(); - } - - private bool CanAcceptOAuth() - { - return ShowOAuth; - } - - private void ForgotPassword() - { - Uri passwordResetUri = _url is null - ? new Uri(BitbucketConstants.HelpUrls.PasswordReset) - : new Uri(_url, BitbucketConstants.HelpUrls.DataCenterPasswordReset); - - BrowserUtils.OpenDefaultBrowser(_environment, passwordResetUri); - } - - private void SignUp() - { - Uri signUpUri = _url is null - ? new Uri(BitbucketConstants.HelpUrls.SignUp) - : new Uri(_url, BitbucketConstants.HelpUrls.DataCenterLogin); - - BrowserUtils.OpenDefaultBrowser(_environment, signUpUri); - } - - public Uri Url - { - get => _url; - set => SetAndRaisePropertyChanged(ref _url, value); - } - - public string UserName - { - get => _userName; - set => SetAndRaisePropertyChanged(ref _userName, value); - } - - public string Password - { - get => _password; - set => SetAndRaisePropertyChanged(ref _password, value); - } - - /// - /// Show the OAuth option. - /// - public bool ShowOAuth - { - get => _showOAuth; - set => SetAndRaisePropertyChanged(ref _showOAuth, value); - } - - /// - /// Show the basic authentication options. - /// - public bool ShowBasic - { - get => _showBasic; - set => SetAndRaisePropertyChanged(ref _showBasic, value); - } - - public AuthenticationModes SelectedMode { get; private set; } - - /// - /// Start the process to validate the username/password - /// - public RelayCommand LoginCommand { get; } - - /// - /// Cancel the authentication attempt. - /// - public ICommand CancelCommand { get; } - - /// - /// Use OAuth authentication instead of username/password. - /// - public ICommand OAuthCommand { get; } - - /// - /// Hyperlink to the Bitbucket forgotten password process. - /// - public ICommand ForgotPasswordCommand { get; } - - /// - /// Hyperlink to the Bitbucket sign up process. - /// - public ICommand SignUpCommand { get; } - } -} diff --git a/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml b/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml deleted file mode 100644 index 717500b7b..000000000 --- a/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - -