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 @@
-
-
-
-
-
-
-
-
-
-
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
-
-
-
-
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
).
-
-
-
-
-
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
-
-
-
-
-
-
-
-
-
-
- Authentication Successful
- Git Credential Manager has been successfully authenticated. You may now close this page.
-
-
-
-
-
-
-]]>
-
-
-
-
- Bitbucket Authentication
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml.cs b/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml.cs
deleted file mode 100644
index 87e596b99..000000000
--- a/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Atlassian.Bitbucket.UI.ViewModels;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-using GitCredentialManager;
-using GitCredentialManager.UI.Controls;
-
-namespace Atlassian.Bitbucket.UI.Views
-{
- public partial class CredentialsView : UserControl, IFocusable
- {
- public CredentialsView()
- {
- InitializeComponent();
- }
-
- public void SetFocus()
- {
- if (!(DataContext is CredentialsViewModel vm))
- {
- return;
- }
-
- if (vm.ShowOAuth)
- {
- _authModesTabControl.SelectedIndex = 0;
- _oauthLoginButton.Focus();
- }
- else if (vm.ShowBasic)
- {
- _authModesTabControl.SelectedIndex = 1;
- if (string.IsNullOrWhiteSpace(vm.UserName))
- {
- // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293
- if (!PlatformUtils.IsMacOS())
- _userNameTextBox.Focus();
- }
- else
- {
- // Workaround: https://github.com/git-ecosystem/git-credential-manager/issues/1293
- if (!PlatformUtils.IsMacOS())
- _passwordTextBox.Focus();
- }
- }
- }
- }
-}
diff --git a/src/shared/Core.Tests/ApplicationTests.cs b/src/shared/Core.Tests/ApplicationTests.cs
deleted file mode 100644
index f983e8d54..000000000
--- a/src/shared/Core.Tests/ApplicationTests.cs
+++ /dev/null
@@ -1,303 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace GitCredentialManager.Tests
-{
- public class ApplicationTests
- {
- [Fact]
- public async Task Application_ConfigureAsync_NoHelpers_AddsEmptyAndGcm()
- {
- const string emptyHelper = "";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(2, actualValues.Count);
- Assert.Equal(emptyHelper, actualValues[0]);
- Assert.Equal(executablePath, actualValues[1]);
- }
-
- [Fact]
- public async Task Application_ConfigureAsync_Gcm_AddsEmptyBeforeGcm()
- {
- const string emptyHelper = "";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List {executablePath};
-
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(2, actualValues.Count);
- Assert.Equal(emptyHelper, actualValues[0]);
- Assert.Equal(executablePath, actualValues[1]);
- }
-
- [Fact]
- public async Task Application_ConfigureAsync_EmptyAndGcm_DoesNothing()
- {
- const string emptyHelper = "";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- emptyHelper, executablePath
- };
-
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(2, actualValues.Count);
- Assert.Equal(emptyHelper, actualValues[0]);
- Assert.Equal(executablePath, actualValues[1]);
- }
-
- [Fact]
- public async Task Application_ConfigureAsync_EmptyAndGcmWithOthersBefore_DoesNothing()
- {
- const string emptyHelper = "";
- const string beforeHelper = "foo";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- beforeHelper, emptyHelper, executablePath
- };
-
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(3, actualValues.Count);
- Assert.Equal(beforeHelper, actualValues[0]);
- Assert.Equal(emptyHelper, actualValues[1]);
- Assert.Equal(executablePath, actualValues[2]);
- }
-
- [Fact]
- public async Task Application_ConfigureAsync_EmptyAndGcmWithOthersAfter_DoesNothing()
- {
- const string emptyHelper = "";
- const string afterHelper = "foo";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- emptyHelper, executablePath, afterHelper
- };
-
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(3, actualValues.Count);
- Assert.Equal(emptyHelper, actualValues[0]);
- Assert.Equal(executablePath, actualValues[1]);
- Assert.Equal(afterHelper, actualValues[2]);
- }
-
- [Fact]
- public async Task Application_ConfigureAsync_EmptyAndGcmWithOthersBeforeAndAfter_DoesNothing()
- {
- const string emptyHelper = "";
- const string beforeHelper = "foo";
- const string afterHelper = "bar";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- beforeHelper, emptyHelper, executablePath, afterHelper
- };
-
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(4, actualValues.Count);
- Assert.Equal(beforeHelper, actualValues[0]);
- Assert.Equal(emptyHelper, actualValues[1]);
- Assert.Equal(executablePath, actualValues[2]);
- Assert.Equal(afterHelper, actualValues[3]);
- }
-
- [Fact]
- public async Task Application_ConfigureAsync_EmptyAndGcmWithEmptyAfter_RemovesExistingGcmAndAddsEmptyAndGcm()
- {
- const string emptyHelper = "";
- const string afterHelper = "foo";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- emptyHelper, executablePath, emptyHelper, afterHelper
- };
-
- await application.ConfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(5, actualValues.Count);
- Assert.Equal(emptyHelper, actualValues[0]);
- Assert.Equal(emptyHelper, actualValues[1]);
- Assert.Equal(afterHelper, actualValues[2]);
- Assert.Equal(emptyHelper, actualValues[3]);
- Assert.Equal(executablePath, actualValues[4]);
- }
-
- [Fact]
- public async Task Application_UnconfigureAsync_NoHelpers_DoesNothing()
- {
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
- await application.UnconfigureAsync(ConfigurationTarget.User);
-
- Assert.Empty(context.Git.Configuration.Global);
- }
-
- [Fact]
- public async Task Application_UnconfigureAsync_Gcm_RemovesGcm()
- {
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List {executablePath};
-
- await application.UnconfigureAsync(ConfigurationTarget.User);
-
- Assert.Empty(context.Git.Configuration.Global);
- }
-
- [Fact]
- public async Task Application_UnconfigureAsync_EmptyAndGcm_RemovesEmptyAndGcm()
- {
- const string emptyHelper = "";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List {emptyHelper, executablePath};
-
- await application.UnconfigureAsync(ConfigurationTarget.User);
-
- Assert.Empty(context.Git.Configuration.Global);
- }
-
- [Fact]
- public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersBefore_RemovesEmptyAndGcm()
- {
- const string emptyHelper = "";
- const string beforeHelper = "foo";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- beforeHelper, emptyHelper, executablePath
- };
-
- await application.UnconfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Single(actualValues);
- Assert.Equal(beforeHelper, actualValues[0]);
- }
-
- [Fact]
- public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersAfterBefore_RemovesGcmOnly()
- {
- const string emptyHelper = "";
- const string afterHelper = "bar";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- emptyHelper, executablePath, afterHelper
- };
-
- await application.UnconfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(2, actualValues.Count);
- Assert.Equal(emptyHelper, actualValues[0]);
- Assert.Equal(afterHelper, actualValues[1]);
- }
-
- [Fact]
- public async Task Application_UnconfigureAsync_EmptyAndGcmWithOthersBeforeAndAfter_RemovesGcmOnly()
- {
- const string emptyHelper = "";
- const string beforeHelper = "foo";
- const string afterHelper = "bar";
- const string executablePath = "/usr/local/share/gcm-core/git-credential-manager";
- string key = $"{Constants.GitConfiguration.Credential.SectionName}.{Constants.GitConfiguration.Credential.Helper}";
-
- var context = new TestCommandContext {AppPath = executablePath};
- IConfigurableComponent application = new Application(context);
-
- context.Git.Configuration.Global[key] = new List
- {
- beforeHelper, emptyHelper, executablePath, afterHelper
- };
-
- await application.UnconfigureAsync(ConfigurationTarget.User);
-
- Assert.Single(context.Git.Configuration.Global);
- Assert.True(context.Git.Configuration.Global.TryGetValue(key, out var actualValues));
- Assert.Equal(3, actualValues.Count);
- Assert.Equal(beforeHelper, actualValues[0]);
- Assert.Equal(emptyHelper, actualValues[1]);
- Assert.Equal(afterHelper, actualValues[2]);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/AuthenticationBaseTests.cs b/src/shared/Core.Tests/Authentication/AuthenticationBaseTests.cs
deleted file mode 100644
index bc4e9e90a..000000000
--- a/src/shared/Core.Tests/Authentication/AuthenticationBaseTests.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using GitCredentialManager.Authentication;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication
-{
- public class AuthenticationBaseTests
- {
- [Theory]
- [InlineData("foo", "foo")]
- [InlineData("foo bar", "\"foo bar\"")]
- [InlineData("foo\nbar", "\"foo\nbar\"")]
- [InlineData("foo\rbar", "\"foo\rbar\"")]
- [InlineData("foo\tbar", "\"foo\tbar\"")]
- [InlineData("foo\" bar", "\"foo\\\" bar\"")]
- [InlineData("foo\"", "\"foo\\\"\"")]
- [InlineData("\"foo", "\"\\\"foo\"")]
- [InlineData("foo\\", "\"foo\\\\\"")]
- [InlineData("foo\\\"", "\"foo\\\\\\\"\"")]
- public void AuthenticationBase_QuoteCmdArg(string input, string expected)
- {
- string actual = AuthenticationBase.QuoteCmdArg(input);
- Assert.Equal(expected, actual);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs b/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs
deleted file mode 100644
index ba42b3b05..000000000
--- a/src/shared/Core.Tests/Authentication/BasicAuthenticationTests.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
-using GitCredentialManager.Authentication;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication
-{
- public class BasicAuthenticationTests
- {
- [Fact]
- public async Task BasicAuthentication_GetCredentials_NullResource_ThrowsException()
- {
- var context = new TestCommandContext();
- var basicAuth = new BasicAuthentication(context);
-
- await Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(null));
- }
-
- [Fact]
- public async Task BasicAuthentication_GetCredentials_NonDesktopSession_ResourceAndUserName_PasswordPromptReturnsCredentials()
- {
- const string testResource = "https://example.com";
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
-
- var context = new TestCommandContext {SessionManager = {IsDesktopSession = false}};
- context.Terminal.SecretPrompts["Password"] = testPassword; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
-
- var basicAuth = new BasicAuthentication(context);
-
- ICredential credential = await basicAuth.GetCredentialsAsync(testResource, testUserName);
-
- Assert.Equal(testUserName, credential.Account);
- Assert.Equal(testPassword, credential.Password);
- }
-
- [Fact]
- public async Task BasicAuthentication_GetCredentials_NonDesktopSession_Resource_UserPassPromptReturnsCredentials()
- {
- const string testResource = "https://example.com";
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
-
- var context = new TestCommandContext {SessionManager = {IsDesktopSession = false}};
- context.Terminal.Prompts["Username"] = testUserName;
- context.Terminal.SecretPrompts["Password"] = testPassword; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
-
- var basicAuth = new BasicAuthentication(context);
-
- ICredential credential = await basicAuth.GetCredentialsAsync(testResource);
-
- Assert.Equal(testUserName, credential.Account);
- Assert.Equal(testPassword, credential.Password);
- }
-
- [Fact]
- public async Task BasicAuthentication_GetCredentials_NonDesktopSession_NoTerminalPrompts_ThrowsException()
- {
- const string testResource = "https://example.com";
-
- var context = new TestCommandContext
- {
- SessionManager = {IsDesktopSession = false},
- Settings = {IsInteractionAllowed = false},
- };
-
- var basicAuth = new BasicAuthentication(context);
-
- await Assert.ThrowsAsync(() => basicAuth.GetCredentialsAsync(testResource));
- }
-
- [Fact]
- public async Task BasicAuthentication_GetCredentials_DesktopSession_UIHelper_CallsHelper()
- {
- const string testResource = "https://example.com";
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
-
- const string unixHelperPath = "/usr/local/bin/git-credential-manager-ui";
- const string windowsHelperPath = @"C:\Program Files\Git Credential Manager\git-credential-manager-ui.exe";
- string helperPath = PlatformUtils.IsWindows() ? windowsHelperPath : unixHelperPath;
-
- var context = new TestCommandContext
- {
- SessionManager = { IsDesktopSession = true },
- Environment =
- {
- Variables =
- {
- [Constants.EnvironmentVariables.GcmUiHelper] = helperPath
- }
- }
- };
-
- context.FileSystem.Files[helperPath] = Array.Empty();
-
- var auth = new Mock(MockBehavior.Strict, context);
- auth.Setup(x => x.InvokeHelperAsync(
- It.IsAny(),
- $"basic --resource {testResource}",
- It.IsAny(),
- It.IsAny()))
- .ReturnsAsync(
- new Dictionary
- {
- ["username"] = testUserName,
- ["password"] = testPassword
- }
- );
-
- ICredential credential = await auth.Object.GetCredentialsAsync(testResource);
-
- Assert.NotNull(credential);
- Assert.Equal(testUserName, credential.Account);
- Assert.Equal(testPassword, credential.Password);
- }
-
- [Fact]
- public async Task BasicAuthentication_GetCredentials_DesktopSession_UIHelper_UserName_CallsHelper()
- {
- const string testResource = "https://example.com";
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
-
- const string unixHelperPath = "/usr/local/bin/git-credential-manager-ui";
- const string windowsHelperPath = @"C:\Program Files\Git Credential Manager\git-credential-manager-ui.exe";
- string helperPath = PlatformUtils.IsWindows() ? windowsHelperPath : unixHelperPath;
-
- var context = new TestCommandContext
- {
- SessionManager = { IsDesktopSession = true },
- Environment =
- {
- Variables =
- {
- [Constants.EnvironmentVariables.GcmUiHelper] = helperPath
- }
- }
- };
-
- context.FileSystem.Files[helperPath] = Array.Empty();
-
- var auth = new Mock(MockBehavior.Strict, context);
- auth.Setup(x => x.InvokeHelperAsync(
- It.IsAny(),
- $"basic --resource {testResource} --username {testUserName}",
- It.IsAny(),
- It.IsAny()))
- .ReturnsAsync(
- new Dictionary
- {
- ["username"] = testUserName,
- ["password"] = testPassword
- }
- );
-
- ICredential credential = await auth.Object.GetCredentialsAsync(testResource, testUserName);
-
- Assert.NotNull(credential);
- Assert.Equal(testUserName, credential.Account);
- Assert.Equal(testPassword, credential.Password);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/MicrosoftAuthenticationTests.cs b/src/shared/Core.Tests/Authentication/MicrosoftAuthenticationTests.cs
deleted file mode 100644
index 0e1a70659..000000000
--- a/src/shared/Core.Tests/Authentication/MicrosoftAuthenticationTests.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using GitCredentialManager.Authentication;
-using GitCredentialManager.Tests.Objects;
-using Microsoft.Identity.Client.AppConfig;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication
-{
- public class MicrosoftAuthenticationTests
- {
- [Fact]
- public async Task MicrosoftAuthentication_GetTokenForUserAsync_NoInteraction_ThrowsException()
- {
- const string authority = "https://login.microsoftonline.com/common";
- const string clientId = "C9E8FDA6-1D46-484C-917C-3DBD518F27C3";
- Uri redirectUri = new Uri("https://localhost");
- string[] scopes = {"user.read"};
- const string userName = null; // No user to ensure we do not use an existing token
-
- var context = new TestCommandContext
- {
- Settings = {IsInteractionAllowed = false},
- };
-
- var msAuth = new MicrosoftAuthentication(context);
-
- await Assert.ThrowsAsync(
- () => msAuth.GetTokenForUserAsync(authority, clientId, redirectUri, scopes, userName, false));
- }
-
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData(" ")]
- [InlineData("system")]
- [InlineData("SYSTEM")]
- [InlineData("sYsTeM")]
- [InlineData("00000000-0000-0000-0000-000000000000")]
- [InlineData("id://00000000-0000-0000-0000-000000000000")]
- [InlineData("ID://00000000-0000-0000-0000-000000000000")]
- [InlineData("Id://00000000-0000-0000-0000-000000000000")]
- public void MicrosoftAuthentication_GetManagedIdentity_ValidSystemId_ReturnsSystemId(string str)
- {
- ManagedIdentityId actual = MicrosoftAuthentication.GetManagedIdentity(str);
- Assert.Equal(ManagedIdentityId.SystemAssigned, actual);
- }
-
- [Theory]
- [InlineData("8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("id://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("ID://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("Id://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("resource://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("RESOURCE://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("rEsOuRcE://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("resource://00000000-0000-0000-0000-000000000000")]
- public void MicrosoftAuthentication_GetManagedIdentity_ValidUserIdByClientId_ReturnsUserId(string str)
- {
- ManagedIdentityId actual = MicrosoftAuthentication.GetManagedIdentity(str);
- Assert.NotNull(actual);
- Assert.NotEqual(ManagedIdentityId.SystemAssigned, actual);
- }
-
- [Theory]
- [InlineData("unknown://8B49DCA0-1298-4A0D-AD6D-934E40230839")]
- [InlineData("this is a string")]
- public void MicrosoftAuthentication_GetManagedIdentity_Invalid_ThrowsArgumentException(string str)
- {
- Assert.Throws(() => MicrosoftAuthentication.GetManagedIdentity(str));
- }
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/OAuth2ClientTests.cs b/src/shared/Core.Tests/Authentication/OAuth2ClientTests.cs
deleted file mode 100644
index be660b99b..000000000
--- a/src/shared/Core.Tests/Authentication/OAuth2ClientTests.cs
+++ /dev/null
@@ -1,457 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using GitCredentialManager.Authentication.OAuth;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication
-{
- public class OAuth2ClientTests
- {
- private const string TestClientId = "9ffe7f11c8";
- private const string TestClientSecret = "62adac63a4614d93833470942a38454f"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
- private static readonly Uri TestRedirectUri = new Uri("http://localhost/oauth-callback");
-
- [Fact]
- public async Task OAuth2Client_GetAuthorizationCodeAsync()
- {
- const string expectedAuthCode = "68c39cbd8d";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- OAuth2Application app = CreateTestApplication();
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AuthCodes.Add(expectedAuthCode);
-
- IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- OAuth2AuthorizationCodeResult result = await client.GetAuthorizationCodeAsync(expectedScopes, browser, null, CancellationToken.None);
-
- Assert.Equal(expectedAuthCode, result.Code);
- }
-
- [Theory]
- [InlineData("http://localhost")]
- [InlineData("http://localhost/")]
- [InlineData("http://localhost/oauth-callback")]
- [InlineData("http://localhost/oauth-callback/")]
- [InlineData("http://127.0.0.1")]
- [InlineData("http://127.0.0.1/")]
- [InlineData("http://127.0.0.1/oauth-callback")]
- [InlineData("http://127.0.0.1/oauth-callback/")]
- public async Task OAuth2Client_GetAuthorizationCodeAsync_RedirectUrlOriginalStringPreserved(string expectedRedirectUrl)
- {
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- OAuth2Application app = new OAuth2Application(TestClientId)
- {
- Secret = TestClientSecret,
- RedirectUris = new[] {new Uri(expectedRedirectUrl)}
- };
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AuthCodes.Add("unused");
- server.AuthorizationEndpointInvoked += (_, request) =>
- {
- IDictionary actualParams = request.RequestUri.GetQueryParameters();
- Assert.True(actualParams.TryGetValue(OAuth2Constants.RedirectUriParameter, out string actualRedirectUri));
- Assert.Equal(expectedRedirectUrl, actualRedirectUri);
- };
-
- IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler);
-
- var redirectUri = new Uri(expectedRedirectUrl);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = new OAuth2Client(
- new HttpClient(httpHandler),
- endpoints,
- TestClientId,
- trace2,
- redirectUri,
- TestClientSecret);
-
- await client.GetAuthorizationCodeAsync(new[] { "unused" }, browser, null, CancellationToken.None);
- }
-
- [Fact]
- public async Task OAuth2Client_GetAuthorizationCodeAsync_ExtraQueryParams()
- {
- const string expectedAuthCode = "68c39cbd8d";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- var extraParams = new Dictionary
- {
- ["param1"] = "value1",
- ["param2"] = "value2",
- ["param3"] = "value3"
- };
-
- OAuth2Application app = CreateTestApplication();
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AuthCodes.Add(expectedAuthCode);
-
- server.AuthorizationEndpointInvoked += (_, request) =>
- {
- IDictionary actualParams = request.RequestUri.GetQueryParameters();
- foreach (var expected in extraParams)
- {
- Assert.True(actualParams.TryGetValue(expected.Key, out string actualValue));
- Assert.Equal(expected.Value, actualValue);
- }
- };
-
- IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- OAuth2AuthorizationCodeResult result = await client.GetAuthorizationCodeAsync(expectedScopes, browser, extraParams, CancellationToken.None);
-
- Assert.Equal(expectedAuthCode, result.Code);
- }
-
- [Fact]
- public async Task OAuth2Client_GetAuthorizationCodeAsync_ExtraQueryParams_OverrideStandardArgs_ThrowsException()
- {
- const string expectedAuthCode = "68c39cbd8d";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- var extraParams = new Dictionary
- {
- ["param1"] = "value1",
- [OAuth2Constants.ClientIdParameter] = "value2",
- ["param3"] = "value3"
- };
-
- OAuth2Application app = CreateTestApplication();
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AuthCodes.Add(expectedAuthCode);
-
- IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- await Assert.ThrowsAsync(() =>
- client.GetAuthorizationCodeAsync(expectedScopes, browser, extraParams, CancellationToken.None));
- }
-
- [Fact]
- public async Task OAuth2Client_GetDeviceCodeAsync()
- {
- const string expectedUserCode = "254583";
- const string expectedDeviceCode = "6d1e34151aff4f41b9f186e177a0b15d";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- OAuth2Application app = CreateTestApplication();
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.UserCodes.Add(expectedUserCode);
- server.TokenGenerator.DeviceCodes.Add(expectedDeviceCode);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- OAuth2DeviceCodeResult result = await client.GetDeviceCodeAsync(expectedScopes, CancellationToken.None);
-
- Assert.Equal(expectedUserCode, result.UserCode);
- Assert.Equal(expectedDeviceCode, result.DeviceCode);
- }
-
- [Fact]
- public async Task OAuth2Client_GetTokenByAuthorizationCodeAsync()
- {
- const string authCode = "a63ef59691";
- const string expectedAccessToken = "LET_ME_IN";
- const string expectedRefreshToken = "REFRESH_ME";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- OAuth2Application app = CreateTestApplication();
- app.AuthGrants.Add(new OAuth2Application.AuthCodeGrant(authCode, expectedScopes));
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- var authCodeResult = new OAuth2AuthorizationCodeResult(authCode, TestRedirectUri);
- OAuth2TokenResult result = await client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None);
-
- Assert.NotNull(result);
- Assert.Equal(expectedScopes, result.Scopes);
- Assert.Equal(expectedAccessToken, result.AccessToken);
- Assert.Equal(expectedRefreshToken, result.RefreshToken);
- }
-
- [Fact]
- public async Task OAuth2Client_GetTokenByRefreshTokenAsync()
- {
- const string oldAccessToken = "OLD_LET_ME_IN";
- const string oldRefreshToken = "OLD_REFRESH_ME";
- const string expectedAccessToken = "NEW_LET_ME_IN";
- const string expectedRefreshToken = "NEW_REFRESH_ME";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- // Setup an existing access and refresh token
- OAuth2Application app = CreateTestApplication();
- app.AccessTokens[oldAccessToken] = oldRefreshToken;
- app.RefreshTokens[oldRefreshToken] = expectedScopes;
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- OAuth2TokenResult result = await client.GetTokenByRefreshTokenAsync(oldRefreshToken, CancellationToken.None);
-
- Assert.NotNull(result);
- Assert.Equal(expectedScopes, result.Scopes);
- Assert.Equal(expectedAccessToken, result.AccessToken);
- Assert.Equal(expectedRefreshToken, result.RefreshToken);
- }
-
- [Fact]
- public async Task OAuth2Client_GetTokenByDeviceCodeAsync()
- {
- const string expectedUserCode = "342728";
- const string expectedDeviceCode = "ad6498533bf54f4db53e49612a4acfb0";
- const string expectedAccessToken = "LET_ME_IN";
- const string expectedRefreshToken = "REFRESH_ME";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- var grant = new OAuth2Application.DeviceCodeGrant(expectedUserCode, expectedDeviceCode, expectedScopes);
-
- OAuth2Application app = CreateTestApplication();
- app.DeviceGrants.Add(grant);
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.UserCodes.Add(expectedUserCode);
- server.TokenGenerator.DeviceCodes.Add(expectedDeviceCode);
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- var deviceCodeResult = new OAuth2DeviceCodeResult(expectedDeviceCode, expectedUserCode, null, null);
-
- Task resultTask = client.GetTokenByDeviceCodeAsync(deviceCodeResult, CancellationToken.None);
-
- // Simulate the user taking some time to sign in with the user code
- Thread.Sleep(1000);
- server.SignInDeviceWithUserCode(expectedUserCode);
-
- OAuth2TokenResult result = await resultTask;
-
- Assert.NotNull(result);
- Assert.Equal(expectedScopes, result.Scopes);
- Assert.Equal(expectedAccessToken, result.AccessToken);
- Assert.Equal(expectedRefreshToken, result.RefreshToken);
- }
-
- [Fact]
- public async Task OAuth2Client_E2E_InteractiveWebFlowAndRefresh()
- {
- const string expectedAuthCode = "e78a711d11";
- const string expectedAccessToken1 = "LET_ME_IN-1";
- const string expectedAccessToken2 = "LET_ME_IN-2";
- const string expectedRefreshToken1 = "REFRESH_ME-1";
- const string expectedRefreshToken2 = "REFRESH_ME-2";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- OAuth2Application app = CreateTestApplication();
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.AuthCodes.Add(expectedAuthCode);
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken1);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken1);
-
- IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- OAuth2AuthorizationCodeResult authCodeResult = await client.GetAuthorizationCodeAsync(
- expectedScopes, browser, null, CancellationToken.None);
-
- OAuth2TokenResult result1 = await client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None);
-
- Assert.NotNull(result1);
- Assert.Equal(expectedScopes, result1.Scopes);
- Assert.Equal(expectedAccessToken1, result1.AccessToken);
- Assert.Equal(expectedRefreshToken1, result1.RefreshToken);
-
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken2);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken2);
-
- OAuth2TokenResult result2 = await client.GetTokenByRefreshTokenAsync(result1.RefreshToken, CancellationToken.None);
- Assert.NotNull(result2);
- Assert.Equal(expectedScopes, result2.Scopes);
- Assert.Equal(expectedAccessToken2, result2.AccessToken);
- Assert.Equal(expectedRefreshToken2, result2.RefreshToken);
- }
-
- [Fact]
- public async Task OAuth2Client_E2E_DeviceFlowAndRefresh()
- {
- const string expectedUserCode = "736998";
- const string expectedDeviceCode = "db6558b2a1d649758394ac3c2d9e00b1";
- const string expectedAccessToken1 = "LET_ME_IN-1";
- const string expectedAccessToken2 = "LET_ME_IN-2";
- const string expectedRefreshToken1 = "REFRESH_ME-1";
- const string expectedRefreshToken2 = "REFRESH_ME-2";
-
- var baseUri = new Uri("https://example.com");
- OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri);
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
-
- string[] expectedScopes = {"read", "write", "delete"};
-
- OAuth2Application app = CreateTestApplication();
-
- var server = new TestOAuth2Server(endpoints);
- server.RegisterApplication(app);
- server.Bind(httpHandler);
- server.TokenGenerator.UserCodes.Add(expectedUserCode);
- server.TokenGenerator.DeviceCodes.Add(expectedDeviceCode);
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken1);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken1);
-
- var trace2 = new NullTrace2();
- OAuth2Client client = CreateClient(httpHandler, endpoints, trace2);
-
- OAuth2DeviceCodeResult deviceResult = await client.GetDeviceCodeAsync(expectedScopes, CancellationToken.None);
-
- // Simulate the user taking some time to sign in with the user code
- Thread.Sleep(1000);
- server.SignInDeviceWithUserCode(deviceResult.UserCode);
-
- OAuth2TokenResult result1 = await client.GetTokenByDeviceCodeAsync(deviceResult, CancellationToken.None);
-
- Assert.NotNull(result1);
- Assert.Equal(expectedScopes, result1.Scopes);
- Assert.Equal(expectedAccessToken1, result1.AccessToken);
- Assert.Equal(expectedRefreshToken1, result1.RefreshToken);
-
- server.TokenGenerator.AccessTokens.Add(expectedAccessToken2);
- server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken2);
-
- OAuth2TokenResult result2 = await client.GetTokenByRefreshTokenAsync(result1.RefreshToken, CancellationToken.None);
- Assert.NotNull(result2);
- Assert.Equal(expectedScopes, result2.Scopes);
- Assert.Equal(expectedAccessToken2, result2.AccessToken);
- Assert.Equal(expectedRefreshToken2, result2.RefreshToken);
- }
-
- #region Helpers
-
- private static OAuth2Application CreateTestApplication() => new OAuth2Application(TestClientId)
- {
- Secret = TestClientSecret,
- RedirectUris = new[] {TestRedirectUri}
- };
-
- private static OAuth2Client CreateClient(HttpMessageHandler httpHandler, OAuth2ServerEndpoints endpoints, ITrace2 trace2, IOAuth2CodeGenerator generator = null)
- {
- return new OAuth2Client(new HttpClient(httpHandler), endpoints, TestClientId, trace2, TestRedirectUri, TestClientSecret)
- {
- CodeGenerator = generator
- };
- }
-
- private static OAuth2ServerEndpoints CreateEndpoints(Uri baseUri)
- {
- Uri authEndpoint = new Uri(baseUri, "/oauth/v2.0/authorize");
- Uri tokenEndpoint = new Uri(baseUri, "/oauth/v2.0/access_token");
- Uri deviceEndpoint = new Uri(baseUri, "/oauth/v2.0/authorize_device");
-
- return new OAuth2ServerEndpoints(authEndpoint, tokenEndpoint)
- {DeviceAuthorizationEndpoint = deviceEndpoint};
- }
-
- #endregion
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/OAuth2CryptographicCodeGeneratorTests.cs b/src/shared/Core.Tests/Authentication/OAuth2CryptographicCodeGeneratorTests.cs
deleted file mode 100644
index 367c4cdd7..000000000
--- a/src/shared/Core.Tests/Authentication/OAuth2CryptographicCodeGeneratorTests.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using GitCredentialManager.Authentication.OAuth;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication
-{
- public class OAuth2CryptographicCodeGeneratorTests
- {
- private const string ValidBase64UrlCharsNoPad = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
-
- [Fact]
- public void OAuth2CryptographicCodeGenerator_CreateNonce_IsUnique()
- {
- var generator = new OAuth2CryptographicCodeGenerator();
-
- // Create a bunch of nonce values
- var nonces = new string[32];
- for (int i = 0; i < nonces.Length; i++)
- {
- nonces[i] = generator.CreateNonce();
- }
-
- // There should be no duplicates
- string[] uniqueNonces = nonces.Distinct().ToArray();
- Assert.Equal(uniqueNonces, nonces);
- }
-
- [Fact]
- public void OAuth2CryptographicCodeGenerator_CreatePkceCodeVerifier_IsUniqueBase64UrlStringWithoutPaddingAndLengthBetween43And128()
- {
- var generator = new OAuth2CryptographicCodeGenerator();
-
- // Create a bunch of verifiers
- var verifiers = new string[32];
- for (int i = 0; i < verifiers.Length; i++)
- {
- string v = generator.CreatePkceCodeVerifier();
-
- // Assert the verifier is a base64url string without padding
- char[] vs = v.ToCharArray();
- Assert.All(vs, x => Assert.Contains(x, ValidBase64UrlCharsNoPad));
-
- // Assert the verifier is a string of length [43, 128] (inclusive)
- Assert.InRange(v.Length, 43, 128);
-
- verifiers[i] = v;
- }
-
- // There should be no duplicates
- string[] uniqueVerifiers = verifiers.Distinct().ToArray();
- Assert.Equal(uniqueVerifiers, verifiers);
- }
-
- [Fact]
- public void OAuth2CryptographicCodeGenerator_CreatePkceCodeChallenge_Plain_ReturnsVerifierUnchanged()
- {
- var generator = new OAuth2CryptographicCodeGenerator();
-
- var verifier = generator.CreatePkceCodeVerifier();
- var challenge = generator.CreatePkceCodeChallenge(OAuth2PkceChallengeMethod.Plain, verifier);
-
- Assert.Equal(verifier, challenge);
- }
-
- [Fact]
- public void OAuth2CryptographicCodeGenerator_CreatePkceCodeChallenge_Sha256_ReturnsBase64UrlEncodedSha256HashOfAsciiVerifier()
- {
- var generator = new OAuth2CryptographicCodeGenerator();
-
- var verifier = generator.CreatePkceCodeVerifier();
-
- byte[] verifierAsciiBytes = Encoding.ASCII.GetBytes(verifier);
- byte[] hashedBytes;
- using (var sha256 = SHA256.Create())
- {
- hashedBytes = sha256.ComputeHash(verifierAsciiBytes);
- }
-
- var expectedChallenge = Base64UrlConvert.Encode(hashedBytes, false);
- var actualChallenge = generator.CreatePkceCodeChallenge(OAuth2PkceChallengeMethod.Sha256, verifier);
-
- Assert.Equal(expectedChallenge, actualChallenge);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/OAuth2SystemWebBrowserTests.cs b/src/shared/Core.Tests/Authentication/OAuth2SystemWebBrowserTests.cs
deleted file mode 100644
index 2d80c94ab..000000000
--- a/src/shared/Core.Tests/Authentication/OAuth2SystemWebBrowserTests.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using GitCredentialManager.Authentication.OAuth;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication;
-
-public class OAuth2SystemWebBrowserTests
-{
- [Fact]
- public void OAuth2SystemWebBrowser_UpdateRedirectUri_NonLoopback_ThrowsError()
- {
- var env = new TestEnvironment();
- var options = new OAuth2WebBrowserOptions();
- var browser = new OAuth2SystemWebBrowser(env, options);
-
- Assert.Throws(() => browser.UpdateRedirectUri(new Uri("http://example.com")));
- }
-
- [Theory]
- [InlineData("http://localhost:1234", "http://localhost:1234")]
- [InlineData("http://localhost:1234/", "http://localhost:1234/")]
- [InlineData("http://localhost:1234/oauth-callback", "http://localhost:1234/oauth-callback")]
- [InlineData("http://localhost:1234/oauth-callback/", "http://localhost:1234/oauth-callback/")]
- [InlineData("http://127.0.0.7:1234", "http://127.0.0.7:1234")]
- [InlineData("http://127.0.0.7:1234/", "http://127.0.0.7:1234/")]
- [InlineData("http://127.0.0.7:1234/oauth-callback", "http://127.0.0.7:1234/oauth-callback")]
- [InlineData("http://127.0.0.7:1234/oauth-callback/", "http://127.0.0.7:1234/oauth-callback/")]
- public void OAuth2SystemWebBrowser_UpdateRedirectUri_SpecificPort(string input, string expected)
- {
- var env = new TestEnvironment();
- var options = new OAuth2WebBrowserOptions();
- var browser = new OAuth2SystemWebBrowser(env, options);
-
- Uri actualUri = browser.UpdateRedirectUri(new Uri(input));
-
- Assert.Equal(expected, actualUri.OriginalString);
- }
-
- [Theory]
- [InlineData("http://localhost")]
- [InlineData("http://localhost/")]
- [InlineData("http://localhost/oauth-callback")]
- [InlineData("http://localhost/oauth-callback/")]
- [InlineData("http://127.0.0.7")]
- [InlineData("http://127.0.0.7/")]
- [InlineData("http://127.0.0.7/oauth-callback")]
- [InlineData("http://127.0.0.7/oauth-callback/")]
- public void OAuth2SystemWebBrowser_UpdateRedirectUri_AnyPort(string input)
- {
- var env = new TestEnvironment();
- var options = new OAuth2WebBrowserOptions();
- var browser = new OAuth2SystemWebBrowser(env, options);
-
- var inputUri = new Uri(input);
- Uri actualUri = browser.UpdateRedirectUri(inputUri);
-
- Assert.Equal(inputUri.Scheme, actualUri.Scheme);
- Assert.Equal(inputUri.Host, actualUri.Host);
- Assert.Equal(
- inputUri.GetComponents(UriComponents.Path, UriFormat.Unescaped),
- actualUri.GetComponents(UriComponents.Path, UriFormat.Unescaped)
- );
- Assert.False(actualUri.IsDefaultPort);
- }
-}
diff --git a/src/shared/Core.Tests/Authentication/WindowsIntegratedAuthenticationTests.cs b/src/shared/Core.Tests/Authentication/WindowsIntegratedAuthenticationTests.cs
deleted file mode 100644
index ffb5e8775..000000000
--- a/src/shared/Core.Tests/Authentication/WindowsIntegratedAuthenticationTests.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
-using GitCredentialManager.Authentication;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Authentication
-{
- public class WindowsIntegratedAuthenticationTests
- {
- private const string NegotiateHeader = "Negotiate [test-challenge-string]";
- private const string NtlmHeader = "NTLM [test-challenge-string]";
-
- [Fact]
- public async Task WindowsIntegratedAuthentication_GetIsSupportedAsync_NullUri_ThrowsException()
- {
- var context = new TestCommandContext();
- var wiaAuth = new WindowsIntegratedAuthentication(context);
-
- await Assert.ThrowsAsync(() => wiaAuth.GetIsSupportedAsync(null));
- }
-
- [Fact]
- public async Task WindowsIntegratedAuthentication_GetIsSupportedAsync_NegotiateAndNtlm_ReturnsTrue()
- {
- await TestGetIsSupportedAsync(new[] {NegotiateHeader, NtlmHeader}, expected: true);
-
- // Also check with the headers in the other order
- await TestGetIsSupportedAsync(new[] {NtlmHeader, NegotiateHeader}, expected: true);
- }
-
- [Fact]
- public async Task WindowsIntegratedAuthentication_Windows_GetIsSupportedAsync_Ntlm_ReturnsTrue()
- {
- await TestGetIsSupportedAsync(new[]{NtlmHeader}, expected: true);
- }
-
- [Fact]
- public async Task WindowsIntegratedAuthentication_Windows_GetIsSupportedAsync_Negotiate_ReturnsTrue()
- {
- await TestGetIsSupportedAsync(new[]{NegotiateHeader}, expected: true);
- }
-
- [Fact]
- public async Task WindowsIntegratedAuthentication_Windows_GetIsSupportedAsync_NoHeaders_ReturnsFalse()
- {
- await TestGetIsSupportedAsync(new string[0], expected: false);
- }
-
- [Fact]
- public async Task WindowsIntegratedAuthentication_Windows_GetIsSupportedAsync_NoWiaHeaders_ReturnsFalse()
- {
- await TestGetIsSupportedAsync(
- new[]
- {
- "Bearer",
- "Bearer foo",
- "NotNTLM test test NTLM test",
- "NotNegotiate test test Negotiate test",
- "NotKerberos test test Negotiate test"
- },
- expected: false);
- }
-
- #region Helpers
-
- private static async Task TestGetIsSupportedAsync(string[] wwwAuthHeaders, bool expected)
- {
- var context = new TestCommandContext();
- var uri = new Uri("https://example.com");
-
- var wiaResponse = new HttpResponseMessage(HttpStatusCode.Unauthorized);
- foreach (string headerValue in wwwAuthHeaders)
- {
- wiaResponse.Headers.WwwAuthenticate.ParseAdd(headerValue);
- }
-
- var httpHandler = new TestHttpMessageHandler {ThrowOnUnexpectedRequest = true};
- httpHandler.Setup(HttpMethod.Head, uri, wiaResponse);
-
- context.HttpClientFactory.MessageHandler = httpHandler;
- var wiaAuth = new WindowsIntegratedAuthentication(context);
-
- bool actual = await wiaAuth.GetIsSupportedAsync(uri);
-
- Assert.Equal(expected, actual);
- httpHandler.AssertRequest(HttpMethod.Head, uri, expectedNumberOfCalls: 1);
- }
-
- #endregion
- }
-}
diff --git a/src/shared/Core.Tests/Base64UrlConvertTests.cs b/src/shared/Core.Tests/Base64UrlConvertTests.cs
deleted file mode 100644
index f9728f8ac..000000000
--- a/src/shared/Core.Tests/Base64UrlConvertTests.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Xunit;
-
-namespace GitCredentialManager.Tests
-{
- public class Base64UrlConvertTests
- {
- [Theory]
- [InlineData(new byte[0], "")]
- [InlineData(new byte[]{4}, "BA==")]
- [InlineData(new byte[]{4,5}, "BAU=")]
- [InlineData(new byte[]{4,5,6}, "BAUG")]
- [InlineData(new byte[]{4,5,6,7}, "BAUGBw==")]
- [InlineData(new byte[]{4,5,6,7,8}, "BAUGBwg=")]
- [InlineData(new byte[]{4,5,6,7,8,9}, "BAUGBwgJ")]
- public void Base64UrlConvert_Encode_WithPadding(byte[] data, string expected)
- {
- string actual = Base64UrlConvert.Encode(data, includePadding: true);
- Assert.Equal(expected, actual);
- }
-
- [Theory]
- [InlineData(new byte[0], "")]
- [InlineData(new byte[]{4}, "BA")]
- [InlineData(new byte[]{4,5}, "BAU")]
- [InlineData(new byte[]{4,5,6}, "BAUG")]
- [InlineData(new byte[]{4,5,6,7}, "BAUGBw")]
- [InlineData(new byte[]{4,5,6,7,8}, "BAUGBwg")]
- [InlineData(new byte[]{4,5,6,7,8,9}, "BAUGBwgJ")]
- public void Base64UrlConvert_Encode_WithoutPadding(byte[] data, string expected)
- {
- string actual = Base64UrlConvert.Encode(data, includePadding: false);
- Assert.Equal(expected, actual);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Commands/ConfigureCommandTests.cs b/src/shared/Core.Tests/Commands/ConfigureCommandTests.cs
deleted file mode 100644
index 8c41f7178..000000000
--- a/src/shared/Core.Tests/Commands/ConfigureCommandTests.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.Threading.Tasks;
-using GitCredentialManager.Commands;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Commands
-{
- public class ConfigureCommandTests
- {
- [Fact]
- public async Task ConfigureCommand_ExecuteAsync_User_InvokesConfigurationServiceConfigureUser()
- {
- var configService = new Mock();
- configService.Setup(x => x.ConfigureAsync(It.IsAny()))
- .Returns(Task.CompletedTask)
- .Verifiable();
-
- var context = new TestCommandContext();
- var command = new ConfigureCommand(context, configService.Object);
-
- await command.ExecuteAsync(false);
-
- configService.Verify(x => x.ConfigureAsync(ConfigurationTarget.User), Times.Once);
- }
-
- [Fact]
- public async Task ConfigureCommand_ExecuteAsync_System_InvokesConfigurationServiceConfigureSystem()
- {
- var configService = new Mock();
- configService.Setup(x => x.ConfigureAsync(It.IsAny()))
- .Returns(Task.CompletedTask)
- .Verifiable();
-
- var context = new TestCommandContext();
- var command = new ConfigureCommand(context, configService.Object);
-
- await command.ExecuteAsync(true);
-
- configService.Verify(x => x.ConfigureAsync(ConfigurationTarget.System), Times.Once);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs b/src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs
deleted file mode 100644
index 0118e9d85..000000000
--- a/src/shared/Core.Tests/Commands/DiagnoseCommandTests.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using System;
-using System.Net.Http;
-using System.Security.AccessControl;
-using System.Text;
-using GitCredentialManager.Diagnostics;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace Core.Tests.Commands;
-
-public class DiagnoseCommandTests
-{
- [Fact]
- public void NetworkingDiagnostic_SendHttpRequest_Primary_OK()
- {
- var primaryUriString = "http://example.com";
- var sb = new StringBuilder();
- var context = new TestCommandContext();
- var networkingDiagnostic = new NetworkingDiagnostic(context);
- var primaryUri = new Uri(primaryUriString);
- var httpHandler = new TestHttpMessageHandler();
- var httpResponse = new HttpResponseMessage();
- var expected = $"Sending HEAD request to {primaryUriString}... OK{Environment.NewLine}";
-
- httpHandler.Setup(HttpMethod.Head, primaryUri, httpResponse);
-
- networkingDiagnostic.SendHttpRequest(sb, new HttpClient(httpHandler));
-
- httpHandler.AssertRequest(HttpMethod.Head, primaryUri, expectedNumberOfCalls: 1);
- Assert.Contains(expected, sb.ToString());
- }
-
- [Fact]
- public void NetworkingDiagnostic_SendHttpRequest_Backup_OK()
- {
- var primaryUriString = "http://example.com";
- var backupUriString = "http://httpforever.com";
- var sb = new StringBuilder();
- var context = new TestCommandContext();
- var networkingDiagnostic = new NetworkingDiagnostic(context);
- var primaryUri = new Uri(primaryUriString);
- var backupUri = new Uri(backupUriString);
- var httpHandler = new TestHttpMessageHandler { SimulatePrimaryUriFailure = true };
- var httpResponse = new HttpResponseMessage();
- var expected = $"Sending HEAD request to {primaryUriString}... warning: HEAD request failed{Environment.NewLine}" +
- $"Sending HEAD request to {backupUriString}... OK{Environment.NewLine}";
-
- httpHandler.Setup(HttpMethod.Head, primaryUri, httpResponse);
- httpHandler.Setup(HttpMethod.Head, backupUri, httpResponse);
-
- networkingDiagnostic.SendHttpRequest(sb, new HttpClient(httpHandler));
-
- httpHandler.AssertRequest(HttpMethod.Head, primaryUri, expectedNumberOfCalls: 1);
- httpHandler.AssertRequest(HttpMethod.Head, backupUri, expectedNumberOfCalls: 1);
- Assert.Contains(expected, sb.ToString());
- }
-
- [Fact]
- public void NetworkingDiagnostic_SendHttpRequest_No_Network()
- {
- var primaryUriString = "http://example.com";
- var backupUriString = "http://httpforever.com";
- var sb = new StringBuilder();
- var context = new TestCommandContext();
- var networkingDiagnostic = new NetworkingDiagnostic(context);
- var primaryUri = new Uri(primaryUriString);
- var backupUri = new Uri(backupUriString);
- var httpHandler = new TestHttpMessageHandler { SimulateNoNetwork = true };
- var httpResponse = new HttpResponseMessage();
- var expected = $"Sending HEAD request to {primaryUriString}... warning: HEAD request failed{Environment.NewLine}" +
- $"Sending HEAD request to {backupUriString}... warning: HEAD request failed{Environment.NewLine}";
-
- httpHandler.Setup(HttpMethod.Head, primaryUri, httpResponse);
- httpHandler.Setup(HttpMethod.Head, backupUri, httpResponse);
-
- networkingDiagnostic.SendHttpRequest(sb, new HttpClient(httpHandler));
-
- httpHandler.AssertRequest(HttpMethod.Head, primaryUri, expectedNumberOfCalls: 1);
- httpHandler.AssertRequest(HttpMethod.Head, backupUri, expectedNumberOfCalls: 1);
- Assert.Contains(expected, sb.ToString());
- }
-}
diff --git a/src/shared/Core.Tests/Commands/EraseCommandTests.cs b/src/shared/Core.Tests/Commands/EraseCommandTests.cs
deleted file mode 100644
index 25f8c7b06..000000000
--- a/src/shared/Core.Tests/Commands/EraseCommandTests.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using GitCredentialManager.Commands;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Commands
-{
- public class EraseCommandTests
- {
- [Fact]
- public async Task EraseCommand_ExecuteAsync_CallsHostProvider()
- {
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
- var stdin = $"protocol=http\nhost=example.com\nusername={testUserName}\npassword={testPassword}\n\n";
- var expectedInput = new InputArguments(new Dictionary
- {
- ["protocol"] = "http",
- ["host"] = "example.com",
- ["username"] = testUserName,
- ["password"] = testPassword // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
- });
-
- var providerMock = new Mock();
- providerMock.Setup(x => x.EraseCredentialAsync(It.IsAny()))
- .Returns(Task.CompletedTask);
- var providerRegistry = new TestHostProviderRegistry {Provider = providerMock.Object};
- var context = new TestCommandContext
- {
- Streams = {In = stdin}
- };
-
- var command = new EraseCommand(context, providerRegistry);
-
- await command.ExecuteAsync();
-
- providerMock.Verify(
- x => x.EraseCredentialAsync(It.Is(y => AreInputArgumentsEquivalent(expectedInput, y))),
- Times.Once);
- }
-
- private static bool AreInputArgumentsEquivalent(InputArguments a, InputArguments b)
- {
- return a.Protocol == b.Protocol &&
- a.Host == b.Host &&
- a.Path == b.Path &&
- a.UserName == b.UserName &&
- a.Password == b.Password;
- }
- }
-}
diff --git a/src/shared/Core.Tests/Commands/GetCommandTests.cs b/src/shared/Core.Tests/Commands/GetCommandTests.cs
deleted file mode 100644
index f80824794..000000000
--- a/src/shared/Core.Tests/Commands/GetCommandTests.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using GitCredentialManager.Commands;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Commands
-{
- public class GetCommandTests
- {
- [Fact]
- public async Task GetCommand_ExecuteAsync_CallsHostProviderAndWritesCredential()
- {
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
- ICredential testCredential = new GitCredential(testUserName, testPassword);
- var stdin = $"protocol=http\nhost=example.com\n\n";
- var expectedStdOutDict = new Dictionary
- {
- ["protocol"] = "http",
- ["host"] = "example.com",
- ["username"] = testUserName,
- ["password"] = testPassword
- };
-
- var providerMock = new Mock();
- providerMock.Setup(x => x.GetCredentialAsync(It.IsAny()))
- .ReturnsAsync(testCredential);
- var providerRegistry = new TestHostProviderRegistry {Provider = providerMock.Object};
- var context = new TestCommandContext
- {
- Streams = {In = stdin}
- };
-
- var command = new GetCommand(context, providerRegistry);
-
- await command.ExecuteAsync();
-
- IDictionary actualStdOutDict = ParseDictionary(context.Streams.Out);
-
- providerMock.Verify(x => x.GetCredentialAsync(It.IsAny()), Times.Once);
- Assert.Equal(expectedStdOutDict, actualStdOutDict);
- }
-
- #region Helpers
-
- private static IDictionary ParseDictionary(StringBuilder sb) => ParseDictionary(sb.ToString());
-
- private static IDictionary ParseDictionary(string str) => new StringReader(str).ReadDictionary();
-
- #endregion
- }
-}
diff --git a/src/shared/Core.Tests/Commands/GitCommandBaseTests.cs b/src/shared/Core.Tests/Commands/GitCommandBaseTests.cs
deleted file mode 100644
index 090125ed6..000000000
--- a/src/shared/Core.Tests/Commands/GitCommandBaseTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using GitCredentialManager.Commands;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Commands
-{
- public class GitCommandBaseTests
- {
- [Fact]
- public async Task GitCommandBase_ExecuteAsync_CallsExecuteInternalAsyncWithCorrectArgs()
- {
- var mockContext = new Mock();
- var mockStreams = new Mock();
- var mockProvider = new Mock();
- var mockHostRegistry = new Mock();
-
- mockHostRegistry.Setup(x => x.GetProviderAsync(It.IsAny()))
- .ReturnsAsync(mockProvider.Object)
- .Verifiable();
-
- mockProvider.Setup(x => x.IsSupported(It.IsAny()))
- .Returns(true);
-
- string standardIn = "protocol=test\nhost=example.com\npath=a/b/c\n\n";
- TextReader standardInReader = new StringReader(standardIn);
-
- mockStreams.Setup(x => x.In).Returns(standardInReader);
- mockContext.Setup(x => x.Streams).Returns(mockStreams.Object);
- mockContext.Setup(x => x.Trace).Returns(Mock.Of());
- mockContext.Setup(x => x.Settings).Returns(Mock.Of());
-
- GitCommandBase testCommand = new TestCommand(mockContext.Object, mockHostRegistry.Object)
- {
- VerifyExecuteInternalAsync = (input, provider) =>
- {
- Assert.Same(mockProvider.Object, provider);
- Assert.Equal("test", input.Protocol);
- Assert.Equal("example.com", input.Host);
- Assert.Equal("a/b/c", input.Path);
- }
- };
-
- await testCommand.ExecuteAsync();
- }
-
- [Fact]
- public async Task GitCommandBase_ExecuteAsync_ConfiguresSettingsRemoteUri()
- {
- var mockContext = new Mock();
- var mockStreams = new Mock();
- var mockProvider = new Mock();
- var mockSettings = new Mock();
- var mockHostRegistry = new Mock();
-
- mockHostRegistry.Setup(x => x.GetProviderAsync(It.IsAny()))
- .ReturnsAsync(mockProvider.Object);
-
- string standardIn = "protocol=test\nhost=example.com\npath=a/b/c\n\n";
- TextReader standardInReader = new StringReader(standardIn);
-
- var remoteUri = new Uri("test://example.com/a/b/c");
-
- mockSettings.SetupProperty(x => x.RemoteUri);
-
- mockStreams.Setup(x => x.In).Returns(standardInReader);
- mockContext.Setup(x => x.Streams).Returns(mockStreams.Object);
- mockContext.Setup(x => x.Trace).Returns(Mock.Of());
- mockContext.Setup(x => x.Settings).Returns(mockSettings.Object);
-
- GitCommandBase testCommand = new TestCommand(mockContext.Object, mockHostRegistry.Object);
-
- await testCommand.ExecuteAsync();
-
- Assert.Equal(remoteUri, mockSettings.Object.RemoteUri);
- }
-
- private class TestCommand : GitCommandBase
- {
- public TestCommand(ICommandContext context, IHostProviderRegistry hostProviderRegistry)
- : base(context, "test", null, hostProviderRegistry)
- {
- }
-
- protected override Task ExecuteInternalAsync(InputArguments input, IHostProvider provider)
- {
- VerifyExecuteInternalAsync?.Invoke(input, provider);
- return Task.CompletedTask;
- }
-
- public Action VerifyExecuteInternalAsync { get; set; }
- }
- }
-}
diff --git a/src/shared/Core.Tests/Commands/StoreCommandTests.cs b/src/shared/Core.Tests/Commands/StoreCommandTests.cs
deleted file mode 100644
index e7cd4acad..000000000
--- a/src/shared/Core.Tests/Commands/StoreCommandTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using GitCredentialManager.Commands;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Commands
-{
- public class StoreCommandTests
- {[Fact]
- public async Task StoreCommand_ExecuteAsync_CallsHostProvider()
- {
- const string testUserName = "john.doe";
- const string testPassword = "letmein123"; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Fake credential")]
- var stdin = $"protocol=http\nhost=example.com\nusername={testUserName}\npassword={testPassword}\n\n";
- var expectedInput = new InputArguments(new Dictionary
- {
- ["protocol"] = "http",
- ["host"] = "example.com",
- ["username"] = testUserName,
- ["password"] = testPassword
- });
-
- var providerMock = new Mock();
- providerMock.Setup(x => x.StoreCredentialAsync(It.IsAny()))
- .Returns(Task.CompletedTask);
- var providerRegistry = new TestHostProviderRegistry {Provider = providerMock.Object};
- var context = new TestCommandContext
- {
- Streams = {In = stdin}
- };
-
- var command = new StoreCommand(context, providerRegistry);
-
- await command.ExecuteAsync();
-
- providerMock.Verify(
- x => x.StoreCredentialAsync(It.Is(y => AreInputArgumentsEquivalent(expectedInput, y))),
- Times.Once);
- }
-
- bool AreInputArgumentsEquivalent(InputArguments a, InputArguments b)
- {
- return a.Protocol == b.Protocol &&
- a.Host == b.Host &&
- a.Path == b.Path &&
- a.UserName == b.UserName &&
- a.Password == b.Password;
- }
- }
-}
diff --git a/src/shared/Core.Tests/Commands/UnconfigureCommandTests.cs b/src/shared/Core.Tests/Commands/UnconfigureCommandTests.cs
deleted file mode 100644
index 14e2a4cb7..000000000
--- a/src/shared/Core.Tests/Commands/UnconfigureCommandTests.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System.Threading.Tasks;
-using GitCredentialManager.Commands;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests.Commands
-{
- public class UnconfigureCommandTests
- {
- [Fact]
- public async Task UnconfigureCommand_ExecuteAsync_User_InvokesConfigurationServiceUnconfigureUser()
- {
- var configService = new Mock();
- configService.Setup(x => x.UnconfigureAsync(It.IsAny()))
- .Returns(Task.CompletedTask)
- .Verifiable();
-
- var context = new TestCommandContext();
- var command = new UnconfigureCommand(context, configService.Object);
-
- await command.ExecuteAsync(false);
-
- configService.Verify(x => x.UnconfigureAsync(ConfigurationTarget.User), Times.Once);
- }
-
- [Fact]
- public async Task UnconfigureCommand_ExecuteAsync_System_InvokesConfigurationServiceUnconfigureSystem()
- {
- var configService = new Mock();
- configService.Setup(x => x.UnconfigureAsync(It.IsAny()))
- .Returns(Task.CompletedTask)
- .Verifiable();
-
- var context = new TestCommandContext();
- var command = new UnconfigureCommand(context, configService.Object);
-
- await command.ExecuteAsync(true);
-
- configService.Verify(x => x.UnconfigureAsync(ConfigurationTarget.System), Times.Once);
- }
- }
-}
diff --git a/src/shared/Core.Tests/ConfigurationServiceTests.cs b/src/shared/Core.Tests/ConfigurationServiceTests.cs
deleted file mode 100644
index 1453ada6c..000000000
--- a/src/shared/Core.Tests/ConfigurationServiceTests.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using System.Threading.Tasks;
-using GitCredentialManager.Tests.Objects;
-using Moq;
-using Xunit;
-
-namespace GitCredentialManager.Tests
-{
- public class ConfigurationServiceTests
- {
- [Fact]
- public async Task ConfigurationService_ConfigureAsync_System_ComponentsAreConfiguredWithSystem()
- {
- var context = new TestCommandContext();
- var service = new ConfigurationService(context);
-
- var component1 = new Mock();
- var component2 = new Mock();
- var component3 = new Mock();
-
- service.AddComponent(component1.Object);
- service.AddComponent(component2.Object);
- service.AddComponent(component3.Object);
-
- await service.ConfigureAsync(ConfigurationTarget.System);
-
- component1.Verify(x => x.ConfigureAsync(ConfigurationTarget.System),
- Times.Once);
- component2.Verify(x => x.ConfigureAsync(ConfigurationTarget.System),
- Times.Once);
- component3.Verify(x => x.ConfigureAsync(ConfigurationTarget.System),
- Times.Once);
- }
-
- [Fact]
- public async Task ConfigurationService_ConfigureAsync_User_ComponentsAreConfiguredWithUser()
- {
- var context = new TestCommandContext();
- var service = new ConfigurationService(context);
-
- var component1 = new Mock();
- var component2 = new Mock();
- var component3 = new Mock();
-
- service.AddComponent(component1.Object);
- service.AddComponent(component2.Object);
- service.AddComponent(component3.Object);
-
- await service.ConfigureAsync(ConfigurationTarget.User);
-
- component1.Verify(x => x.ConfigureAsync(ConfigurationTarget.User),
- Times.Once);
- component2.Verify(x => x.ConfigureAsync(ConfigurationTarget.User),
- Times.Once);
- component3.Verify(x => x.ConfigureAsync(ConfigurationTarget.User),
- Times.Once);
- }
-
- [Fact]
- public async Task ConfigurationService_UnconfigureAsync_System_ComponentsAreUnconfiguredWithSystem()
- {
- var context = new TestCommandContext();
- var service = new ConfigurationService(context);
-
- var component1 = new Mock();
- var component2 = new Mock();
- var component3 = new Mock();
-
- service.AddComponent(component1.Object);
- service.AddComponent(component2.Object);
- service.AddComponent(component3.Object);
-
- await service.UnconfigureAsync(ConfigurationTarget.System);
-
- component1.Verify(x => x.UnconfigureAsync(ConfigurationTarget.System),
- Times.Once);
- component2.Verify(x => x.UnconfigureAsync(ConfigurationTarget.System),
- Times.Once);
- component3.Verify(x => x.UnconfigureAsync(ConfigurationTarget.System),
- Times.Once);
- }
-
- [Fact]
- public async Task ConfigurationService_UnconfigureAsync_User_ComponentsAreUnconfiguredWithUser()
- {
- var context = new TestCommandContext();
- var service = new ConfigurationService(context);
-
- var component1 = new Mock();
- var component2 = new Mock();
- var component3 = new Mock();
-
- service.AddComponent(component1.Object);
- service.AddComponent(component2.Object);
- service.AddComponent(component3.Object);
-
- await service.UnconfigureAsync(ConfigurationTarget.User);
-
- component1.Verify(x => x.UnconfigureAsync(ConfigurationTarget.User),
- Times.Once);
- component2.Verify(x => x.UnconfigureAsync(ConfigurationTarget.User),
- Times.Once);
- component3.Verify(x => x.UnconfigureAsync(ConfigurationTarget.User),
- Times.Once);
- }
- }
-}
diff --git a/src/shared/Core.Tests/Core.Tests.csproj b/src/shared/Core.Tests/Core.Tests.csproj
deleted file mode 100644
index e3ae7e6b0..000000000
--- a/src/shared/Core.Tests/Core.Tests.csproj
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
- net8.0
- false
- true
- latest
- true
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
diff --git a/src/shared/Core.Tests/CurlCookieTests.cs b/src/shared/Core.Tests/CurlCookieTests.cs
deleted file mode 100644
index 3d161d76d..000000000
--- a/src/shared/Core.Tests/CurlCookieTests.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using GitCredentialManager;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace Core.Tests;
-
-public class CurlCookieParserTests
-{
- [Fact]
- public void CurlCookieParser_EmptyFile_ReturnsNoCookies()
- {
- const string content = "";
-
- var trace = new NullTrace();
- var parser = new CurlCookieParser(trace);
-
- IList actual = parser.Parse(content);
-
- Assert.Empty(actual);
- }
-
- [Fact]
- public void CurlCookieParser_Parse_MissingFields_SkipsInvalidLines()
- {
- const string content =
- // Valid cookie
- ".example.com\tTRUE\t/path/here\tTRUE\t0\tcookie1\tvalue1\n" +
-
- // Missing several fields - not a valid cookie so should be skipped
- ".example.com\tTRUE\tTRUE\tcookie1\tvalue1\n";
-
- var trace = new NullTrace();
- var parser = new CurlCookieParser(trace);
-
- IList actual = parser.Parse(content);
-
- Assert.Single(actual);
- AssertCookie(actual[0], ".example.com", "/path/here", true, 0, "cookie1", "value1");
- }
-
- [Fact]
- public void CurlCookieParser_Parse_MissingFields_ReturnsValidCookiesWithDefaults()
- {
- const string content =
- // Empty path field (default "/")
- ".example.com\tTRUE\t\tTRUE\t852852\tcookie1\tvalue1\n" +
-
- // Empty expiration field (default 0)
- ".example.com\tTRUE\t/path/here\tTRUE\t\tcookie1\tvalue1";
-
- var trace = new NullTrace();
- var parser = new CurlCookieParser(trace);
-
- IList actual = parser.Parse(content);
-
- Assert.Equal(2, actual.Count);
- AssertCookie(actual[0], ".example.com", "/", true, 852852, "cookie1", "value1");
- AssertCookie(actual[1], ".example.com", "/path/here", true, 0, "cookie1", "value1");
- }
-
- [Fact]
- public void CurlCookieParser_Parse_ValidFields_ReturnsValidCookies()
- {
- const string content =
- ".example.com\tTRUE\t/path\tTRUE\t0\tcookie1\tvalue1\n" +
- ".example.com\tfAlSe\t/path\ttRuE\t0\tcookie1\tvalue1\n" +
- ".example.com\tTRUE\t/path\tTRUE\t0\tcookie1 with spaces\tvalue1 with spaces\n" +
- ".example.com\tFALSE\t/path\tTRUE\t0\tcookie1\tvalue1\n" +
- "example.com\tFALSE\t/path\tTRUE\t0\tcookie1\tvalue1\n" +
- "example.com\tTRUE\t/path\tTRUE\t0\tcookie1\tvalue1\n" +
- ".example.com\tTRUE\t/path\tFALSE\t0\tcookie1\tvalue1\n" +
- ".example.com\tTRUE\t/path\tFALSE\t401654\tcookie1\tvalue1\n";
-
- var trace = new NullTrace();
- var parser = new CurlCookieParser(trace);
-
- IList actual = parser.Parse(content);
-
- Assert.Equal(8, actual.Count);
- AssertCookie(actual[0], ".example.com", "/path", true, 0, "cookie1", "value1");
- AssertCookie(actual[1], "example.com", "/path", true, 0, "cookie1", "value1");
- AssertCookie(actual[2], ".example.com", "/path", true, 0, "cookie1 with spaces", "value1 with spaces");
- AssertCookie(actual[3], "example.com", "/path", true, 0, "cookie1", "value1");
- AssertCookie(actual[4], "example.com", "/path", true, 0, "cookie1", "value1");
- AssertCookie(actual[5], "example.com", "/path", true, 0, "cookie1", "value1");
- AssertCookie(actual[6], ".example.com", "/path", false, 0, "cookie1", "value1");
- AssertCookie(actual[7], ".example.com", "/path", false, 401654, "cookie1", "value1");
- }
-
- [Fact]
- public void CurlCookieParser_Parse_Comments_ReturnsCookies()
- {
- const string content =
- // Comment block
- "# This is a cookie file with various comments!\n" +
- "# Lines starting with # are comments, except those that\n" +
- "# start with exactly '#HttpOnly_'.. two #s is a comment still!\n" +
-
- // This is still a comment!
- "##HttpOnly_ <-- this is a comment still!\n" +
-
- // Valid line
- ".example.com\tTRUE\t/\tTRUE\t0\tcookie1\tvalue1\n" +
-
- // Commented out cookie line
- "#.example.com\tTRUE\t/\tTRUE\t0\tcookie1\tvalue1\n" +
-
- // Valid cookie but HTTP only
- "#HttpOnly_.example.com\tTRUE\t/\tTRUE\t0\tcookie1\tvalue1\n";
-
- var trace = new NullTrace();
- var parser = new CurlCookieParser(trace);
-
- IList actual = parser.Parse(content);
-
- Assert.Equal(2, actual.Count);
- AssertCookie(actual[0], ".example.com", "/", true, 0, "cookie1", "value1");
- AssertCookie(actual[1], ".example.com", "/", true, 0, "cookie1", "value1");
- }
-
- private static void AssertCookie(Cookie cookie, string domain, string path, bool secureOnly, long expires, string name, string value)
- {
- Assert.Equal(name, cookie.Name);
- Assert.Equal(value, cookie.Value);
- Assert.Equal(domain, cookie.Domain);
- Assert.Equal(path, cookie.Path);
- Assert.Equal(secureOnly, cookie.Secure);
- Assert.Equal(expires, cookie.Expires.Subtract(DateTime.UnixEpoch).TotalSeconds);
- }
-}
diff --git a/src/shared/Core.Tests/EnsureArgumentTests.cs b/src/shared/Core.Tests/EnsureArgumentTests.cs
deleted file mode 100644
index f26437867..000000000
--- a/src/shared/Core.Tests/EnsureArgumentTests.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using Xunit;
-
-namespace GitCredentialManager.Tests
-{
- public class EnsureArgumentTests
- {
- [Theory]
- [InlineData(5, 0, 10, true, true)]
- [InlineData(0, 0, 10, true, true)]
- [InlineData(10, 0, 10, true, true)]
- [InlineData(0, -10, 0, true, true)]
- [InlineData(-10, -10, 0, true, true)]
- public void EnsureArgument_InRange_DoesNotThrow(int x, int lower, int upper, bool lowerInc, bool upperInc)
- {
- EnsureArgument.InRange(x, nameof(x), lower, upper, lowerInc, upperInc);
- }
-
- [Theory]
- [InlineData(-3, 0, 10, true, true)]
- [InlineData(13, 0, 10, true, true)]
- [InlineData(10, 0, 10, true, false)]
- [InlineData(0, 0, 10, false, true)]
- [InlineData(-10, -10, 0, false, true)]
- [InlineData(0, -10, 0, true, false)]
- public void EnsureArgument_InRange_ThrowsException(int x, int lower, int upper, bool lowerInc, bool upperInc)
- {
- Assert.Throws(
- () => EnsureArgument.InRange(x, nameof(x), lower, upper, lowerInc, upperInc)
- );
- }
- }
-}
diff --git a/src/shared/Core.Tests/EnumerableExtensionsTests.cs b/src/shared/Core.Tests/EnumerableExtensionsTests.cs
deleted file mode 100644
index 2430698ae..000000000
--- a/src/shared/Core.Tests/EnumerableExtensionsTests.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Xunit;
-
-namespace GitCredentialManager.Tests
-{
- public class EnumerableExtensionsTests
- {
- [Fact]
- public void EnumerableExtensions_ConcatMany_Null_ThrowsException()
- {
- Assert.Throws(() => EnumerableExtensions.ConcatMany((IEnumerable)null).ToArray());
- }
-
- [Fact]
- public void EnumerableExtensions_ConcatMany_OneSequence_ReturnsSequence()
- {
- int[] seq1 = {0, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 8, 9};
- int[] expectedResult = seq1;
-
- int[] actualResult = EnumerableExtensions.ConcatMany(seq1).ToArray();
-
- Assert.Equal(expectedResult, actualResult);
- }
-
- [Fact]
- public void EnumerableExtensions_ConcatMany_TwoSequences_ReturnsConcatenateSequences()
- {
- int[] seq1 = {0, 1, 2, 3, 4, 4};
- int[] seq2 = {4, 4, 5, 6, 7, 8, 9};
- int[] expectedResult = {0, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 8, 9};
-
- int[] actualResult = EnumerableExtensions.ConcatMany(seq1, seq2).ToArray();
-
- Assert.Equal(expectedResult, actualResult);
- }
-
- [Fact]
- public void EnumerableExtensions_ConcatMany_ManySequences_ReturnsConcatenateSequences()
- {
- int[] seq1 = {0, 1};
- int[] seq2 = {2, 3};
- int[] seq3 = {4, 4};
- int[] seq4 = {4, 4};
- int[] seq5 = {5, 6};
- int[] seq6 = {7, 8};
- int[] seq7 = {9};
- int[] expectedResult = {0, 1, 2, 3, 4, 4, 4, 4, 5, 6, 7, 8, 9};
-
- int[] actualResult = EnumerableExtensions.ConcatMany(seq1, seq2, seq3, seq4, seq5, seq6, seq7).ToArray();
-
- Assert.Equal(expectedResult, actualResult);
- }
- }
-}
diff --git a/src/shared/Core.Tests/EnvironmentTests.cs b/src/shared/Core.Tests/EnvironmentTests.cs
deleted file mode 100644
index d9b7cb67c..000000000
--- a/src/shared/Core.Tests/EnvironmentTests.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using GitCredentialManager.Interop.Posix;
-using GitCredentialManager.Interop.Windows;
-using GitCredentialManager.Tests.Objects;
-using Xunit;
-
-namespace GitCredentialManager.Tests
-{
- public class EnvironmentTests
- {
- private const string WindowsPathVar = @"C:\Users\john.doe\bin;C:\Windows\system32;C:\Windows";
- private const string WindowsExecName = "foo.exe";
- private const string PosixPathVar = "/home/john.doe/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
- private const string PosixExecName = "foo";
-
- [WindowsFact]
- public void WindowsEnvironment_TryLocateExecutable_NotExists_ReturnFalse()
- {
- var fs = new TestFileSystem();
- var envars = new Dictionary {["PATH"] = WindowsPathVar};
- var env = new WindowsEnvironment(fs, envars);
-
- bool actualResult = env.TryLocateExecutable(WindowsExecName, out string actualPath);
-
- Assert.False(actualResult);
- Assert.Null(actualPath);
- }
-
- [WindowsFact]
- public void WindowsEnvironment_TryLocateExecutable_Exists_ReturnTrueAndPath()
- {
- string expectedPath = @"C:\Windows\system32\foo.exe";
- var fs = new TestFileSystem
- {
- Files = new Dictionary
- {
- [expectedPath] = Array.Empty()
- }
- };
- var envars = new Dictionary {["PATH"] = WindowsPathVar};
- var env = new WindowsEnvironment(fs, envars);
-
- bool actualResult = env.TryLocateExecutable(WindowsExecName, out string actualPath);
-
- Assert.True(actualResult);
- Assert.Equal(expectedPath, actualPath);
- }
-
- [WindowsFact]
- public void WindowsEnvironment_TryLocateExecutable_ExistsMultiple_ReturnTrueAndFirstPath()
- {
- string expectedPath = @"C:\Users\john.doe\bin\foo.exe";
- var fs = new TestFileSystem
- {
- Files = new Dictionary