From 77f2da40a99da49f4f56e8c006f9d269e7057c8a Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:39:27 +0200 Subject: [PATCH 1/3] Update timeout data type to ulong (#462) --- Src/Fido2.Models/AssertionOptions.cs | 2 +- Src/Fido2.Models/CredentialCreateOptions.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Src/Fido2.Models/AssertionOptions.cs b/Src/Fido2.Models/AssertionOptions.cs index cb6e5c5e..03a22b06 100644 --- a/Src/Fido2.Models/AssertionOptions.cs +++ b/Src/Fido2.Models/AssertionOptions.cs @@ -24,7 +24,7 @@ public class AssertionOptions : Fido2ResponseBase /// This member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a hint, and MAY be overridden by the client. /// [JsonPropertyName("timeout")] - public uint Timeout { get; set; } + public ulong Timeout { get; set; } /// /// This OPTIONAL member specifies the relying party identifier claimed by the caller.If omitted, its value will be the CredentialsContainer object’s relevant settings object's origin's effective domain diff --git a/Src/Fido2.Models/CredentialCreateOptions.cs b/Src/Fido2.Models/CredentialCreateOptions.cs index 1625486c..f899a88f 100644 --- a/Src/Fido2.Models/CredentialCreateOptions.cs +++ b/Src/Fido2.Models/CredentialCreateOptions.cs @@ -43,14 +43,14 @@ public sealed class CredentialCreateOptions : Fido2ResponseBase /// This member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a hint, and MAY be overridden by the platform. /// [JsonPropertyName("timeout")] - public long Timeout { get; set; } + public ulong Timeout { get; set; } /// /// This member is intended for use by Relying Parties that wish to express their preference for attestation conveyance.The default is none. /// [JsonPropertyName("attestation")] - public AttestationConveyancePreference Attestation { get; set; } = AttestationConveyancePreference.None; - + public AttestationConveyancePreference Attestation { get; set; } = AttestationConveyancePreference.None; + /// /// This member is intended for use by Relying Parties that wish to select the appropriate authenticators to participate in the create() operation. /// @@ -225,7 +225,7 @@ public ResidentKeyRequirement ResidentKey /// [Obsolete("Use property ResidentKey.")] [JsonPropertyName("requireResidentKey")] - public bool RequireResidentKey + public bool RequireResidentKey { get => _requireResidentKey; set From 85b514974aadb71682ab9c8866d88eb8c3f5c61a Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:40:43 +0200 Subject: [PATCH 2/3] Rename `origChallenge` parameter in `Fido2.MakeNewCredentialAsync` (#460) --- Src/Fido2/Fido2.cs | 6 ++-- Src/Fido2/IFido2.cs | 6 ++-- Test/Attestation/Apple.cs | 4 +-- Test/AuthenticatorResponse.cs | 52 ++++++++++++++--------------- Test/Fido2Tests.cs | 62 +++++++++++++++++------------------ 5 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Src/Fido2/Fido2.cs b/Src/Fido2/Fido2.cs index 44f08f65..abb5891e 100644 --- a/Src/Fido2/Fido2.cs +++ b/Src/Fido2/Fido2.cs @@ -58,18 +58,18 @@ public CredentialCreateOptions RequestNewCredential( /// Verifies the response from the browser/authenticator after creating new credentials. /// /// The attestation response from the authenticator. - /// The original options that was sent to the client. + /// The original options that was sent to the client. /// The delegate used to validate that the CredentialID is unique to this user. /// The used to propagate notifications that the operation should be canceled. /// public async Task MakeNewCredentialAsync( AuthenticatorAttestationRawResponse attestationResponse, - CredentialCreateOptions origChallenge, + CredentialCreateOptions originalOptions, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, CancellationToken cancellationToken = default) { var parsedResponse = AuthenticatorAttestationResponse.Parse(attestationResponse); - var success = await parsedResponse.VerifyAsync(origChallenge, _config, isCredentialIdUniqueToUser, _metadataService, cancellationToken); + var success = await parsedResponse.VerifyAsync(originalOptions, _config, isCredentialIdUniqueToUser, _metadataService, cancellationToken); // todo: Set Errormessage etc. return new MakeNewCredentialResult( diff --git a/Src/Fido2/IFido2.cs b/Src/Fido2/IFido2.cs index 585dc12a..5dcfd463 100644 --- a/Src/Fido2/IFido2.cs +++ b/Src/Fido2/IFido2.cs @@ -9,8 +9,8 @@ namespace Fido2NetLib; public interface IFido2 { AssertionOptions GetAssertionOptions( - IEnumerable allowedCredentials, - UserVerificationRequirement? userVerification, + IEnumerable allowedCredentials, + UserVerificationRequirement? userVerification, AuthenticationExtensionsClientInputs? extensions = null); Task MakeAssertionAsync( @@ -24,7 +24,7 @@ Task MakeAssertionAsync( Task MakeNewCredentialAsync( AuthenticatorAttestationRawResponse attestationResponse, - CredentialCreateOptions origChallenge, + CredentialCreateOptions originalOptions, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, CancellationToken cancellationToken = default); diff --git a/Test/Attestation/Apple.cs b/Test/Attestation/Apple.cs index 97c93517..236ae77a 100644 --- a/Test/Attestation/Apple.cs +++ b/Test/Attestation/Apple.cs @@ -233,7 +233,7 @@ public async Task TestApplePublicKeyMismatch() } }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -271,7 +271,7 @@ public async Task TestApplePublicKeyMismatch() Origins = new HashSet { "https://www.passwordless.dev" }, }); - var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); + var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, originalOptions, callback); } private string[] StackAllocSha256(byte[] authData, byte[] clientDataJson) diff --git a/Test/AuthenticatorResponse.cs b/Test/AuthenticatorResponse.cs index f240de78..cbaea684 100644 --- a/Test/AuthenticatorResponse.cs +++ b/Test/AuthenticatorResponse.cs @@ -87,7 +87,7 @@ public async Task TestAuthenticatorOriginsAsync(string origin, string expectedOr }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -125,7 +125,7 @@ public async Task TestAuthenticatorOriginsAsync(string origin, string expectedOr Origins = new HashSet { expectedOrigin }, }); - var result = await lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback); + var result = await lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback); } [Theory] @@ -190,7 +190,7 @@ public async Task TestAuthenticatorOriginsFail(string origin, string expectedOri }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -228,7 +228,7 @@ public async Task TestAuthenticatorOriginsFail(string origin, string expectedOri Origins = new HashSet { expectedOrigin }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.StartsWith("Fully qualified origin", ex.Message); } @@ -401,7 +401,7 @@ public async Task TestAuthenticatorAttestationResponseInvalidType() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -439,7 +439,7 @@ public async Task TestAuthenticatorAttestationResponseInvalidType() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Same(Fido2ErrorMessages.AttestationResponseTypeNotWebAuthnGet, ex.Message); } @@ -473,7 +473,7 @@ public async Task TestAuthenticatorAttestationResponseInvalidRawId(byte[] value) }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -511,7 +511,7 @@ public async Task TestAuthenticatorAttestationResponseInvalidRawId(byte[] value) Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Same(Fido2ErrorMessages.AttestationResponseIdMissing, ex.Message); } @@ -543,7 +543,7 @@ public async Task TestAuthenticatorAttestationResponseInvalidRawType() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -581,7 +581,7 @@ public async Task TestAuthenticatorAttestationResponseInvalidRawType() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal("AttestationResponse type must be 'public-key'", ex.Message); } @@ -620,7 +620,7 @@ public async Task TestAuthenticatorAttestationResponseRpidMismatch() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -658,7 +658,7 @@ public async Task TestAuthenticatorAttestationResponseRpidMismatch() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal(Fido2ErrorCode.InvalidRpidHash, ex.Code); Assert.Equal(Fido2ErrorMessages.InvalidRpidHash, ex.Message); } @@ -699,7 +699,7 @@ public async Task TestAuthenticatorAttestationResponseNotUserPresentAsync() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -737,7 +737,7 @@ public async Task TestAuthenticatorAttestationResponseNotUserPresentAsync() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal(Fido2ErrorCode.UserPresentFlagNotSet, ex.Code); Assert.Equal(Fido2ErrorMessages.UserPresentFlagNotSet, ex.Message); @@ -778,7 +778,7 @@ public async Task TestAuthenticatorAttestationResponseBackupEligiblePolicyRequir }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -817,7 +817,7 @@ public async Task TestAuthenticatorAttestationResponseBackupEligiblePolicyRequir BackupEligibleCredentialPolicy = Fido2Configuration.CredentialBackupPolicy.Required, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal(Fido2ErrorMessages.BackupEligibilityRequirementNotMet, ex.Message); } @@ -856,7 +856,7 @@ public async Task TestAuthenticatorAttestationResponseBackupEligiblePolicyDisall }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -895,7 +895,7 @@ public async Task TestAuthenticatorAttestationResponseBackupEligiblePolicyDisall BackupEligibleCredentialPolicy = Fido2Configuration.CredentialBackupPolicy.Disallowed, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal(Fido2ErrorMessages.BackupEligibilityRequirementNotMet, ex.Message); } @@ -934,7 +934,7 @@ public async Task TestAuthenticatorAttestationResponseNoAttestedCredentialData() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -972,7 +972,7 @@ public async Task TestAuthenticatorAttestationResponseNoAttestedCredentialData() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal("Attestation flag not set on attestation data", ex.Message); } @@ -1012,7 +1012,7 @@ public async Task TestAuthenticatorAttestationResponseUnknownAttestationType() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -1050,7 +1050,7 @@ public async Task TestAuthenticatorAttestationResponseUnknownAttestationType() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal("Unknown attestation type. Was 'testing'", ex.Message); Assert.Equal(Fido2ErrorCode.UnknownAttestationType, ex.Code); } @@ -1090,7 +1090,7 @@ public async Task TestAuthenticatorAttestationResponseNotUniqueCredId() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -1128,7 +1128,7 @@ public async Task TestAuthenticatorAttestationResponseNotUniqueCredId() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal("CredentialId is not unique to this user", ex.Message); } @@ -1167,7 +1167,7 @@ public async Task TestAuthenticatorAttestationResponseUVRequired() }, }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -1205,7 +1205,7 @@ public async Task TestAuthenticatorAttestationResponseUVRequired() Origins = new HashSet { rp }, }); - var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); + var ex = await Assert.ThrowsAsync(() => lib.MakeNewCredentialAsync(rawResponse, originalOptions, callback)); Assert.Equal("User Verified flag not set in authenticator data and user verification was required", ex.Message); } diff --git a/Test/Fido2Tests.cs b/Test/Fido2Tests.cs index 756d6c89..1d7d144f 100644 --- a/Test/Fido2Tests.cs +++ b/Test/Fido2Tests.cs @@ -74,8 +74,8 @@ static Fido2Tests() new (COSE.KeyType.OKP, COSE.Algorithm.EdDSA, COSE.EllipticCurve.Ed25519), new (COSE.KeyType.EC2, COSE.Algorithm.ES256K, COSE.EllipticCurve.P256K) }; - } - + } + private async Task GetAsync(string filename) { return JsonSerializer.Deserialize(await File.ReadAllTextAsync(filename)); @@ -117,7 +117,7 @@ public byte[] _clientDataJson public byte[] _attToBeSignedHash(HashAlgorithmName alg) { - return CryptoUtils.HashData(alg, _attToBeSigned); + return CryptoUtils.HashData(alg, _attToBeSigned); } public byte[] _credentialID; @@ -125,15 +125,15 @@ public byte[] _attToBeSignedHash(HashAlgorithmName alg) public ushort _signCount; protected Guid _aaguid = new("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); public Extensions GetExtensions() - { + { var extBytes = new CborMap { { "testing", true } }.Encode(); - return new Extensions(extBytes); + return new Extensions(extBytes); } - public AuthenticatorData _authData => new(_rpIdHash, _flags, _signCount, _acd, GetExtensions()); - - public AttestedCredentialData _acd => new(_aaguid, _credentialID, _credentialPublicKey); - + public AuthenticatorData _authData => new(_rpIdHash, _flags, _signCount, _acd, GetExtensions()); + + public AttestedCredentialData _acd => new(_aaguid, _credentialID, _credentialPublicKey); + public Attestation() { _credentialID = RandomNumberGenerator.GetBytes(16); @@ -179,7 +179,7 @@ public async Task MakeAttestationResponseAsync() } }; - var origChallenge = new CredentialCreateOptions + var originalOptions = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection @@ -226,9 +226,9 @@ public async Task MakeAttestationResponseAsync() ServerDomain = rp, ServerName = rp, Origins = new HashSet { rp }, - }); - - var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); + }); + + var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, originalOptions, callback); return credentialMakeResult; } @@ -353,8 +353,8 @@ internal static byte[] SignData(COSE.KeyType kty, COSE.Algorithm alg, byte[] dat default: throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}"); } - } - + } + [Fact] public void TestStringIsSerializable() { @@ -387,11 +387,11 @@ public void TestStringIsSerializable() Assert.Equal(AuthenticatorAttachment.CrossPlatform, y2); // test list of typed strings - var z1 = new[] { - AuthenticatorTransport.Ble, - AuthenticatorTransport.Usb, + var z1 = new[] { + AuthenticatorTransport.Ble, + AuthenticatorTransport.Usb, AuthenticatorTransport.Nfc, - AuthenticatorTransport.Internal + AuthenticatorTransport.Internal }; var zjson = JsonSerializer.Serialize(z1); @@ -464,9 +464,9 @@ public void TestAppleAppAttestProd() var clientDataJson = SHA256.HashData(Encoding.UTF8.GetBytes("1234567890abcdefgh")); var verifier = new AppleAppAttest(); - var ex = Assert.Throws(() => - { - (AttestationType attType, X509Certificate[] trustPath) = verifier.Verify(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataJson); + var ex = Assert.Throws(() => + { + (AttestationType attType, X509Certificate[] trustPath) = verifier.Verify(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataJson); }); const string windowsErrorMessage = "Failed to build chain in Apple AppAttest attestation: A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file."; @@ -822,8 +822,8 @@ public async Task TestAssertionResponse() Assert.Equal(new byte[] { 0xf1, 0xd0 }, avr.CredentialId); Assert.Equal("1", avr.SignCount.ToString("X")); } - } - + } + internal static async Task MakeAssertionResponseAsync( COSE.KeyType kty, COSE.Algorithm alg, @@ -846,16 +846,16 @@ internal static async Task MakeAssertionResponseAsync( { case COSE.KeyType.EC2: { - ecdsa ??= MakeECDsa(alg, crv); - + ecdsa ??= MakeECDsa(alg, crv); + var ecParams = ecdsa.ExportParameters(true); cpk = MakeCredentialPublicKey(kty, alg, crv, ecParams.Q.X, ecParams.Q.Y); break; } case COSE.KeyType.RSA: { - rsa ??= RSA.Create(); - + rsa ??= RSA.Create(); + var rsaParams = rsa.ExportParameters(true); cpk = MakeCredentialPublicKey(kty, alg, rsaParams.Modulus, rsaParams.Exponent); break; @@ -894,7 +894,7 @@ internal static async Task MakeAssertionResponseAsync( var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData); var hashedClientDataJson = SHA256.HashData(clientDataJson); - byte[] data = DataHelper.Concat(authData, hashedClientDataJson); + byte[] data = DataHelper.Concat(authData, hashedClientDataJson); byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey); var userHandle = new byte[16]; @@ -935,12 +935,12 @@ internal static async Task MakeAssertionResponseAsync( } internal static void MakeEdDSA(out byte[] privateKeySeed, out byte[] publicKey, out byte[] expandedPrivateKey) - { + { privateKeySeed = new byte[32]; RandomNumberGenerator.Fill(privateKeySeed); var key = Key.Create(SignatureAlgorithm.Ed25519, new KeyCreationParameters() { ExportPolicy = KeyExportPolicies.AllowPlaintextExport }); expandedPrivateKey = key.Export(KeyBlobFormat.RawPrivateKey); - publicKey = key.Export(KeyBlobFormat.RawPublicKey); + publicKey = key.Export(KeyBlobFormat.RawPublicKey); } internal static ECDsa MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) From 145034a388c38ecd859a44bc290c6f9a59bddca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Fri, 17 Nov 2023 18:02:01 +0100 Subject: [PATCH 3/3] Added github workflow for building, testing and packing (#464) * Added github workflow for building, testing and packing * switch to master * Adde manual runner alternative * change logger * fix * Don't pack automatically * added readme to dirProps * Remove readme --- .github/workflows/main.yml | 195 +++++++++++++++++++++++++++++++++++++ Src/Directory.Build.props | 2 +- 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..f07919ea --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,195 @@ +name: master + +on: + workflow_dispatch: + inputs: + force_version: + description: "The version to use" + required: true + default: "0.0.0-test" + type: string + push: + branches: + - master + pull_request: + branches: + - master + release: + types: + - published + +env: + # Setting these variables allows .NET CLI to use rich color codes in console output + TERM: xterm + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true + # Skip boilerplate output + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + # Determine version + version: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Determine stable version + id: stable-version + if: ${{ github.event_name == 'release' }} + run: | + if ! [[ "${{ github.event.release.tag_name }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z].*)?$ ]]; then + echo "Invalid version: ${{ github.event.release.tag_name }}" + exit 1 + fi + + echo "version=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + + - name: Determine prerelease version + id: pre-version + if: ${{ github.event_name != 'release' }} + run: | + hash="${{ github.event.pull_request.head.sha || github.sha }}" + echo "version=0.0.0-ci-${hash:0:7}" >> $GITHUB_OUTPUT + + outputs: + version: ${{ github.event.inputs.force_version || steps.stable-version.outputs.version || steps.pre-version.outputs.version }} + + # Check formatting +# format: +# runs-on: ubuntu-latest +# permissions: +# contents: read + +# steps: +# - name: Checkout +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Install .NET +# uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + +# - name: Validate format +# run: dotnet format --verify-no-changes + + # Run tests + test: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + # Windows runners don't support Linux Docker containers (needed for tests), + # so we currently cannot run tests on Windows. + # - windows-latest + + runs-on: ${{ matrix.os }} + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Install .NET + uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + + - name: Run restore + run: dotnet restore + + - name: Run build + run: > + dotnet build + --no-restore + --configuration Release + + - name: Run tests + run: > + dotnet test + --no-restore + --no-build + --configuration Release + ${{ runner.os == 'Windows' && '-p:IncludeNetCoreAppTargets=false' || '' }} + --logger "trx;LogFileName=pw-test-results.trx" + -- + RunConfiguration.CollectSourceInformation=true + + # Pack the output into NuGet packages + pack: + needs: version + runs-on: ubuntu-latest + permissions: + actions: write + contents: read + + steps: + - name: Checkout + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Install .NET + uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + + - name: Run restore + run: dotnet restore + + - name: Run build + run: > + dotnet build + --no-restore + --configuration Release + -p:ContinuousIntegrationBuild=true + -p:Version=${{ needs.version.outputs.version }} + + - name: Run pack + run: > + dotnet pack + -p:Version=${{ needs.version.outputs.version }} + -p:ContinuousIntegrationBuild=true + --no-restore + --no-build + --configuration Release + + - name: Upload artifacts + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: packages + path: "**/*.nupkg" + + # Deploy the NuGet packages to the corresponding registries + deploy: + needs: + # Technically, it's not required for the format job to succeed for us to push the package, + # so we may consider removing it as a prerequisite here. + # - format + - test + - pack + + runs-on: ubuntu-latest + permissions: + actions: read + packages: write + + steps: + - name: Download artifacts + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: packages + + - name: Install .NET + uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 + + # Publish to GitHub package registry every time, whether it's a prerelease + # version or a stable release version. + - name: Publish packages (GitHub Registry) + run: > + dotnet nuget push **/*.nupkg + --source https://nuget.pkg.github.com/passwordless-lib/index.json + --api-key ${{ secrets.GITHUB_TOKEN }} + + # Only publish to NuGet on stable releases + # - name: Publish packages (NuGet Registry) + # if: ${{ github.event_name == 'release' }} + # run: > + # dotnet nuget push **/*.nupkg + # --source https://api.nuget.org/v3/index.json + # --api-key ${{ secrets.nuget_api_key }} \ No newline at end of file diff --git a/Src/Directory.Build.props b/Src/Directory.Build.props index 94c54bc0..00c23c19 100644 --- a/Src/Directory.Build.props +++ b/Src/Directory.Build.props @@ -14,6 +14,6 @@ true - true + false \ No newline at end of file