diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml index 8e86d9329..e7d80bb5e 100644 --- a/.azure-pipelines/release.yml +++ b/.azure-pipelines/release.yml @@ -47,3 +47,12 @@ jobs: name: VSEng-MicroBuildVS2017 steps: - template: templates/osx/pack.signed/step4-signpack.yml + +- job: osx_step5 + displayName: macOS (Prepare for distribution) + dependsOn: osx_step4 + condition: succeeded() + pool: + vmImage: macOS 10.13 + steps: + - template: templates/osx/pack.signed/step5-dist.yml diff --git a/.azure-pipelines/templates/osx/compile.yml b/.azure-pipelines/templates/osx/compile.yml index e07e8bb34..16f3f8b3f 100644 --- a/.azure-pipelines/templates/osx/compile.yml +++ b/.azure-pipelines/templates/osx/compile.yml @@ -19,4 +19,4 @@ steps: projects: 'Git-Credential-Manager.sln' arguments: '--configuration=Mac$(configuration)' publishTestResults: true - #testRunTitle: 'Unit tests - common (macOS)' # option not yet available + testRunTitle: 'Unit tests - common (macOS)' diff --git a/.azure-pipelines/templates/osx/pack.signed/step4-signpack.yml b/.azure-pipelines/templates/osx/pack.signed/step4-signpack.yml index 46ee10a9f..339d84354 100644 --- a/.azure-pipelines/templates/osx/pack.signed/step4-signpack.yml +++ b/.azure-pipelines/templates/osx/pack.signed/step4-signpack.yml @@ -30,28 +30,8 @@ steps: Remove-Item $dir\gcmcorepkg.zip -Force displayName: 'Unzip signed package file' - - task: DownloadPipelineArtifact@1 - displayName: Download signed payload - inputs: - buildType: 'current' - artifactName: 'tmp.macpayload_signed' - downloadPath: '$(Build.StagingDirectory)\payload' - - - task: DownloadPipelineArtifact@1 - displayName: Download symbols - inputs: - buildType: 'current' - artifactName: 'tmp.macsymbols' - downloadPath: '$(Build.StagingDirectory)\symbols' - - - script: | - xcopy "$(Build.StagingDirectory)\pkg\*.pkg" "$(Build.StagingDirectory)\publish\" - xcopy "$(Build.StagingDirectory)\payload" "$(Build.StagingDirectory)\publish\payload\" - xcopy "$(Build.StagingDirectory)\symbols" "$(Build.StagingDirectory)\publish\payload.sym\" - displayName: Prepare final build artifact - - task: PublishPipelineArtifact@0 - displayName: Publish signed installer artifacts + displayName: Upload signed installer inputs: - artifactName: 'Installer.Mac.Signed' - targetPath: '$(Build.StagingDirectory)\publish' + artifactName: 'tmp.macinstaller_signed' + targetPath: '$(Build.StagingDirectory)\pkg' diff --git a/.azure-pipelines/templates/osx/pack.signed/step5-dist.yml b/.azure-pipelines/templates/osx/pack.signed/step5-dist.yml new file mode 100644 index 000000000..d049cf693 --- /dev/null +++ b/.azure-pipelines/templates/osx/pack.signed/step5-dist.yml @@ -0,0 +1,36 @@ +steps: + - task: DownloadPipelineArtifact@1 + displayName: Download signed installer + inputs: + buildType: 'current' + artifactName: 'tmp.macinstaller_signed' + downloadPath: '$(Build.StagingDirectory)\pkg' + + - task: DownloadPipelineArtifact@1 + displayName: Download signed payload + inputs: + buildType: 'current' + artifactName: 'tmp.macpayload_signed' + downloadPath: '$(Build.StagingDirectory)\payload' + + - task: DownloadPipelineArtifact@1 + displayName: Download symbols + inputs: + buildType: 'current' + artifactName: 'tmp.macsymbols' + downloadPath: '$(Build.StagingDirectory)\symbols' + + - script: src/osx/SignFiles.Mac/notarize-pkg.sh -id "$(AppleId)" -p "$(AppleIdPassword)" -pkg '$(Build.StagingDirectory)\pkg\*.pkg' + displayName: Notarize and staple installer package + + - script: | + cp "$(Build.StagingDirectory)/pkg/*.pkg" "$(Build.StagingDirectory)/publish/" + cp "$(Build.StagingDirectory)/payload" "$(Build.StagingDirectory)/publish/payload/" + cp "$(Build.StagingDirectory)/symbols" "$(Build.StagingDirectory)/publish/payload.sym/" + displayName: Prepare final build artifact + + - task: PublishPipelineArtifact@0 + displayName: Publish signed installer artifacts + inputs: + artifactName: 'Installer.Mac.Signed' + targetPath: '$(Build.StagingDirectory)/publish' diff --git a/.azure-pipelines/templates/windows/compile.yml b/.azure-pipelines/templates/windows/compile.yml index c0cf0cc62..9425b91ec 100644 --- a/.azure-pipelines/templates/windows/compile.yml +++ b/.azure-pipelines/templates/windows/compile.yml @@ -38,11 +38,13 @@ steps: otherConsoleOptions: '/Framework:.NETCoreApp,Version=2.1' testRunTitle: 'Unit tests - common (Windows)' -- task: VSTest@2 - displayName: Run helpers unit tests - inputs: - testAssemblyVer2: | - out\windows\*.Tests\bin\**\*.Tests.dll - configuration: 'Windows$(configuration)' - otherConsoleOptions: '/Framework:.NETFramework,Version=v4.6.1' - testRunTitle: 'Unit tests - helpers (Windows)' +# Uncomment once Windows helpers have unit tests +# - task: VSTest@2 +# displayName: Run helpers unit tests +# inputs: +# testAssemblyVer2: | +# out\windows\*.Tests\bin\**\*.Tests.dll +# configuration: 'Windows$(configuration)' +# otherConsoleOptions: '/Framework:.NETFramework,Version=v4.6.1' +# testRunTitle: 'Unit tests - helpers (Windows)' + diff --git a/src/osx/Microsoft.Authentication.Helper.Mac/Source/Core/AHAppDelegate.m b/src/osx/Microsoft.Authentication.Helper.Mac/Source/Core/AHAppDelegate.m index c8628f0f9..ac7a92014 100644 --- a/src/osx/Microsoft.Authentication.Helper.Mac/Source/Core/AHAppDelegate.m +++ b/src/osx/Microsoft.Authentication.Helper.Mac/Source/Core/AHAppDelegate.m @@ -2,6 +2,7 @@ // Licensed under the MIT license. #import "AHAppDelegate.h" +#import extern const NSString* kErrorDomain; @@ -21,7 +22,10 @@ -(id)initWithBlock:(AHAppWorkBlock)block logger:(AHLogger*)logger; -(void)run { - NSApplication * application = [NSApplication sharedApplication]; + NSApplication *application = [NSApplication sharedApplication]; + NSMenu *mainMenu = [self createMainMenu]; + [application setMainMenu:mainMenu]; + [self setApplication:application]; [application setDelegate:self]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; @@ -57,6 +61,35 @@ -(void) stop [[self application] postEvent:event atStart:NO]; } +-(NSMenu*) createMainMenu +{ + NSMenu *mainMenu = [NSMenu new]; + + // Create top-level menu items + NSMenuItem *appMenuItem = [NSMenuItem new]; + NSMenuItem *editMenuItem = [NSMenuItem new]; + [mainMenu addItem:appMenuItem]; + [mainMenu addItem:editMenuItem]; + + // Create app menu items + NSMenu *appMenu = [NSMenu new]; + [appMenuItem setSubmenu:appMenu]; + [appMenu addItemWithTitle:@"Quit" + action:@selector(terminate:) + keyEquivalent:@"q"]; + + // Create edit menu items + NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"]; + [editMenuItem setSubmenu:editMenu]; + [editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]; + [editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]; + [editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"]; + [editMenu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""]; + [editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"]; + + return mainMenu; +} + + (NSError*)runDelegate:(AHAppWorkBlock)completionBlock logger:(AHLogger*)logger { AHAppDelegate * delegate = [[AHAppDelegate alloc] initWithBlock:completionBlock logger:logger]; diff --git a/src/osx/SignFiles.Mac/notarize-pkg.sh b/src/osx/SignFiles.Mac/notarize-pkg.sh new file mode 100755 index 000000000..05c4a506d --- /dev/null +++ b/src/osx/SignFiles.Mac/notarize-pkg.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# This file was based on https://github.com/microsoft/BuildXL/blob/8c2348ff04e6ca78726bb945fb2a0f6a55a5c7d6/Private/macOS/notarize.sh +# +# For detailed explanation see: https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution/customizing_the_notarization_workflow + +usage() { + cat < -p -pkg + -id or --appleid # A valid Apple ID email address, account must have correct certificates available + -p or --password # The password for the specified Apple ID or Apple One-Time password (to avoid 2FA) + -pkg or --package # The path to an already signed flat-package +EOM + exit 0 +} + +declare arg_AppleId="" +declare arg_Password="" +declare arg_PackagePath="" + +[ $# -eq 0 ] && { usage; } + +function parseArgs() { + arg_Positional=() + while [[ $# -gt 0 ]]; do + cmd="$1" + case $cmd in + --help | -h) + usage + shift + exit 0 + ;; + --appleid | -id) + arg_AppleId=$2 + shift + ;; + --password | -p) + arg_Password="$2" + shift + ;; + --package | -pkg) + arg_PackagePath="$2" + shift + ;; + *) + arg_Positional+=("$1") + shift + ;; + esac + done +} + +function getPackageId { + local PKG=$(cd "$(dirname "$1")"; pwd)/$(basename "$1") + local PKGDEST=$(mktemp -d | tr -d '\r') + xar -x -f "${PKG}" --exclude '^(?:(?!PackageInfo).)*$' -C "${PKGDEST}" + if [ ! -e "${PKGDEST}/PackageInfo" ]; then + echo "error: can't find 'PackageInfo'; maybe meta-package" + return 1 + fi + cat "${PKGDEST}/PackageInfo" | tr -d '\r' | tr -d '\n' | sed 's:^.*identifier="\([^"]*\)".*$:\1:g' + rm -rf "${PKGDEST}" +} + +parseArgs "$@" + +if [[ -z $arg_AppleId ]]; then + echo "[ERROR] Must supply valid / non-empty Apple ID!" + exit 1 +fi + +if [[ -z $arg_Password ]]; then + echo "[ERROR] Must supply valid / non-empty password!" + exit 1 +fi + +if [[ ! -f "$arg_PackagePath" ]]; then + echo "[ERROR] Must supply valid / non-empty path to package!" + exit 1 +fi + +declare bundle_id=$(getPackageId ${arg_PackagePath}) + +if [[ -z $bundle_id ]]; then + echo "[ERROR] No identifier found in package info!" + exit 1 +fi + +echo "Notarizating $arg_PackagePath" + +echo -e "Current state:\n" +xcrun stapler validate -v "$arg_PackagePath" + +if [[ $? -eq 0 ]]; then + echo "$arg_PackagePath already notarized and stapled, nothing to do!" + exit 0 +fi + +set -e + +declare start_time=$(date +%s) + +declare output="/tmp/progress.xml" + +echo "Uploading package to notarization service, please wait..." +xcrun altool --notarize-app -t osx -f $arg_PackagePath --primary-bundle-id $bundle_id -u $arg_AppleId -p $arg_Password --output-format xml | tee $output + +declare request_id=$(/usr/libexec/PlistBuddy -c "print :notarization-upload:RequestUUID" $output) + +echo "Checking notarization request validity..." +if [[ $request_id =~ ^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$ ]]; then + declare attempts=5 + + while : + do + echo "Waiting a bit before checking on notarization status again..." + + sleep 20 + xcrun altool --notarization-info $request_id -u $arg_AppleId -p $arg_Password --output-format xml | tee $output + + declare status=$(/usr/libexec/PlistBuddy -c "print :notarization-info:Status" $output) + echo "Status: $status" + + if [[ -z $status ]]; then + echo "Left attempts: $attempts" + + if (($attempts <= 0)); then + break + fi + + ((attempts--)) + else + if [[ $status != "in progress" ]]; then + break + fi + fi + done + + declare end_time=$(date +%s) + echo -e "Completed in $(($end_time-$start_time)) seconds\n" + + if [[ "$status" != "success" ]]; then + echo "Error notarizing, exiting..." >&2 + exit 1 + else + declare url=$(/usr/libexec/PlistBuddy -c "print :notarization-info:LogFileURL" $output) + + if [ "$url" ]; then + curl $url + fi + + # Staple the ticket to the package + xcrun stapler staple "$arg_PackagePath" + + echo -e "State after notarization:\n" + xcrun stapler validate -v "$arg_PackagePath" + echo -e "Stapler exit code: $? (must be zero on success!)\n" + fi +else + echo "Invalid request id found in 'altool' output, aborting!" >&2 + exit 1 +fi