private async Task <ApiResponseType> PostDomesticPayment <ApiRequestType, ApiResponseType>( ApiRequestType payment, ApiProfile apiProfile, SoftwareStatementProfile softwareStatementProfile, BankClientProfile bankClientProfile, TokenEndpointResponse tokenEndpointResponse) where ApiRequestType : class where ApiResponseType : class { string payloadJson = JsonConvert.SerializeObject(payment); UriBuilder ub = new UriBuilder(new Uri(apiProfile.BaseUrl + "/domestic-payments")); List <HttpHeader> headers = CreateRequestHeaders( softwareStatement: softwareStatementProfile, payment: payment, client: bankClientProfile, tokenEndpointResponse: tokenEndpointResponse); return(await new HttpRequestBuilder() .SetMethod(HttpMethod.Post) .SetUri(ub.Uri) .SetHeaders(headers) .SetContentType("application/json") .SetContent(payloadJson) .Create() .RequestJsonAsync <ApiResponseType>(client: _apiClient, requestContentIsJson: true)); }
public async Task CreateAsync(AuthorisationCallbackDataPublic redirectData) { redirectData.ArgNotNull(nameof(redirectData)); // Load relevant data objects DomesticConsent consent = (await _domesticConsentRepo.GetAsync(dc => dc.State == redirectData.Response.State)) .FirstOrDefault() ?? throw new KeyNotFoundException( $"Consent with redirect state '{redirectData.Response.State}' not found."); ApiProfile apiProfile = await _apiProfileRepo.GetAsync(consent.ApiProfileId) ?? throw new KeyNotFoundException("API profile cannot be found."); BankClientProfile bankClientProfile = await _openBankingClientRepo.GetAsync(apiProfile.BankClientProfileId) ?? throw new KeyNotFoundException("Bank client profile cannot be found."); SoftwareStatementProfile softwareStatementProfile = _softwareStatementProfileService.GetSoftwareStatementProfile( bankClientProfile.SoftwareStatementProfileId); // Obtain token for consent string redirectUrl = softwareStatementProfile.DefaultFragmentRedirectUrl; TokenEndpointResponse tokenEndpointResponse = await PostAuthCodeGrant( authCode : redirectData.Response.Code, redirectUrl : redirectUrl, client : bankClientProfile); // Update consent with token consent.TokenEndpointResponse = tokenEndpointResponse; await _dbContextService.SaveChangesAsync(); }
public static OpenBankingClientRegistrationClaims CreateRegistrationClaims( string issuerUrl, SoftwareStatementProfile sProfile, bool concatScopes) { sProfile.ArgNotNull(nameof(sProfile)); OpenBankingClientRegistrationClaims registrationClaims = new OpenBankingClientRegistrationClaims { Iss = sProfile.SoftwareStatementPayload.SoftwareId, Aud = issuerUrl, RedirectUris = sProfile.SoftwareStatementPayload.SoftwareRedirectUris, SoftwareId = sProfile.SoftwareStatementPayload.SoftwareId, Scope = sProfile.SoftwareStatementPayload.Scope, /* Scope = concatScopes * ? new[] { sProfile.SoftwareStatementPayload.Scope } * : sProfile.SoftwareStatementPayload.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries), */ SoftwareStatement = sProfile.SoftwareStatement, TlsClientAuthSubjectDn = $"CN={sProfile.SoftwareStatementPayload.SoftwareId},OU={sProfile.SoftwareStatementPayload.OrgId},O=OpenBanking,C=GB" }; return(registrationClaims); }
public async Task Create_IdReturned() { IDbEntityRepository <SoftwareStatementProfile>?repo = Substitute.For <IDbEntityRepository <SoftwareStatementProfile> >(); ISoftwareStatementProfileService?service = Substitute.For <ISoftwareStatementProfileService>(); IDbMultiEntityMethods? dbMethods = Substitute.For <IDbMultiEntityMethods>(); IEntityMapper?mapper = Substitute.For <IEntityMapper>(); SoftwareStatementProfile?resultProfile = new SoftwareStatementProfile(); mapper.Map <SoftwareStatementProfile>(Arg.Any <Models.Public.Request.SoftwareStatementProfile>()) .Returns(resultProfile); CreateSoftwareStatementProfile?interaction = new CreateSoftwareStatementProfile(softwareStatementProfileService: service); Models.Public.Request.SoftwareStatementProfile?profile = new Models.Public.Request.SoftwareStatementProfile { DefaultFragmentRedirectUrl = "http://test.com", SigningKey = "a", SigningKeyId = "b", SigningCertificate = "e30=", TransportKey = "a", TransportCertificate = "a", SoftwareStatement = "e30=.e30=.e30=" }; SoftwareStatementProfileResponse?result = await interaction.CreateAsync(profile); result.Should().NotBeNull(); }
private static List <HttpHeader> CreateRequestHeaders <ApiRequestType>( SoftwareStatementProfile softwareStatement, ApiRequestType payment, BankClientProfile client, TokenEndpointResponse tokenEndpointResponse) where ApiRequestType : class { JwtFactory jwtFactory = new JwtFactory(); string jwt = jwtFactory.CreateJwt( profile: softwareStatement, claims: payment, useOpenBankingJwtHeaders: true); string[] jwsComponents = jwt.Split('.'); string jwsSig = $"{jwsComponents[0]}..{jwsComponents[2]}"; List <HttpHeader> headers = new List <HttpHeader> { new HttpHeader(name: "x-fapi-financial-id", value: client.XFapiFinancialId), new HttpHeader(name: "Authorization", value: "Bearer " + tokenEndpointResponse.AccessToken), new HttpHeader(name: "x-idempotency-key", value: Guid.NewGuid().ToString()), new HttpHeader(name: "x-jws-signature", value: jwsSig) }; return(headers); }
public void GlobalSetup() { _entityMapper = new EntityMapper(); _dataInitiation = CreateDataInitiation(); _risk = CreateRisk(); _domesticConsent = CreateDomesticConsent(); _softwareStatement = CreateSoftwareStatement(); _client = CreateClient(); }
public static SoftwareStatementProfileContext Data( this SoftwareStatementProfileContext context, SoftwareStatementProfile value) { context.ArgNotNull(nameof(context)); value.ArgNotNull(nameof(value)); context.Data = value; return(context); }
public static SoftwareStatementProfileContext TransportKeyInfo( this SoftwareStatementProfileContext context, string keySecretName, string certificate) { SoftwareStatementProfile data = context.ArgNotNull(nameof(context)).GetOrCreateDefault(DataLens); data.TransportKey = keySecretName; data.TransportCertificate = certificate; return(context); }
public async Task SetSoftwareStatementProfileFromSecrets() { // Get active software statement profile IEnumerable <string> profiles = await _activeSoftwareStatementProfilesRepo.GetListAsync( nameof(ActiveSoftwareStatementProfiles.ProfileIds)); if (!profiles.Any()) { throw new KeyNotFoundException("No active software statement profiles found."); } string defaultProfileId = profiles.ElementAt(0); SoftwareStatementProfile softwareStatementProfile = await _softwareStatementProfileRepo.GetAsync(defaultProfileId); SetSoftwareStatementProfile(softwareStatementProfile); }
public Property Validate_ObSigningPem(string value) { Func <bool> rule = () => { var profile = new SoftwareStatementProfile { DefaultFragmentRedirectUrl = "http://test.com", SigningKey = "a", SigningKeyId = "a", SigningCertificate = value, TransportKey = "a", TransportCertificate = "a", SoftwareStatement = "a.b.c" }; var results = new SoftwareStatementProfileValidator().Validate(profile).Errors.ToList(); return(results.Count == 0); }; return(rule.When(!string.IsNullOrWhiteSpace(value))); }
public Property Validate_DefaultFragmentRedirectUrl_InvalidString(string value) { Func <bool> rule = () => { var profile = new SoftwareStatementProfile { DefaultFragmentRedirectUrl = value, SigningKey = "a", SigningKeyId = "a", SigningCertificate = "a", TransportKey = "a", TransportCertificate = "a", SoftwareStatement = "a.b.c" }; var results = new SoftwareStatementProfileValidator().Validate(profile).Errors.ToList(); return(results.Count == 1); }; return(rule.ToProperty()); }
public void SetSoftwareStatementProfile(SoftwareStatementProfile profile) { profile.ArgNotNull(nameof(profile)); Models.Persistent.SoftwareStatementProfile value = _mapper.Map <Models.Persistent.SoftwareStatementProfile>(profile); value.State = "ok"; string[] softwareStatementComponentsBase64 = profile.SoftwareStatement.Split(new[] { '.' }); if (softwareStatementComponentsBase64.Length != 3) { throw new ArgumentException("softwareStatementComponentsBase64 needs 3 components."); } value.SoftwareStatementHeaderBase64 = softwareStatementComponentsBase64[0]; value.SoftwareStatementPayloadBase64 = softwareStatementComponentsBase64[1]; value.SoftwareStatementPayload = value.SoftwareStatementPayloadFromBase64(softwareStatementComponentsBase64[1]); value.SoftwwareStatementSignatureBase64 = softwareStatementComponentsBase64[2]; _defaultSoftwareStatementProfile = value; }
public string CreateJwt <TClaims>( SoftwareStatementProfile profile, TClaims claims, bool useOpenBankingJwtHeaders) where TClaims : class { profile.ArgNotNull(nameof(profile)); claims.ArgNotNull(nameof(claims)); Dictionary <string, object> headers = useOpenBankingJwtHeaders ? CreateOpenBankingJwtHeaders( signingId: profile.SigningKeyId, orgId: profile.SoftwareStatementPayload.OrgId, softwareId: profile.SoftwareStatementPayload.SoftwareId) : CreateJwtHeaders(profile.SigningKeyId); string payloadJson = JsonConvert.SerializeObject( value: claims, settings: new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); X509Certificate2 privateKey = CertificateFactories.GetCertificate2FromPem( privateKey: profile.SigningKey, pem: profile.SigningCertificate); RSA privateKeyRsa = privateKey.GetRSAPrivateKey(); string result = JWT.Encode( payload: payloadJson, key: privateKeyRsa, algorithm: JwsAlgorithm.PS256, extraHeaders: headers); return(result); }
private async Task <ApiResponseType> PostDomesticConsent <ApiRequestType, ApiResponseType>( JwtFactory jwtFactory, SoftwareStatementProfile softwareStatementProfile, ApiRequestType consent, ApiProfile apiProfile, BankClientProfile bankClientProfile, TokenEndpointResponse tokenEndpointResponse) where ApiRequestType : class where ApiResponseType : class { string jwt = jwtFactory.CreateJwt( profile: softwareStatementProfile, claims: consent, useOpenBankingJwtHeaders: true); string[] jwsComponents = jwt.Split('.'); string jwsSignature = $"{jwsComponents[0]}..{jwsComponents[2]}"; UriBuilder ub = new UriBuilder(new Uri(apiProfile.BaseUrl + "/domestic-payment-consents")); string payloadJson = JsonConvert.SerializeObject(consent); List <HttpHeader> headers = new List <HttpHeader> { new HttpHeader(name: "x-fapi-financial-id", value: bankClientProfile.XFapiFinancialId), new HttpHeader(name: "Authorization", value: "Bearer " + tokenEndpointResponse.AccessToken), new HttpHeader(name: "x-idempotency-key", value: Guid.NewGuid().ToString()), new HttpHeader(name: "x-jws-signature", value: jwsSignature) }; return(await new HttpRequestBuilder() .SetMethod(HttpMethod.Post) .SetUri(ub.Uri) .SetHeaders(headers) .SetContentType("application/json") .SetContent(payloadJson) .Create() .RequestJsonAsync <ApiResponseType>(client: _apiClient, requestContentIsJson: true)); }
public SoftwareStatementProfileContext(ISharedContext context) { Context = context.ArgNotNull(nameof(context)); Data = new SoftwareStatementProfile(); }
public async Task <BankClientProfileResponse> CreateAsync(BankClientProfilePublic bankClientProfile) { bankClientProfile.ArgNotNull(nameof(bankClientProfile)); // Load relevant objects SoftwareStatementProfile softwareStatementProfile = _softwareStatementProfileService.GetSoftwareStatementProfile( bankClientProfile.SoftwareStatementProfileId); // STEP 1 // Compute claims associated with Open Banking client // Get OpenID Connect configuration info OpenIdConfiguration openIdConfiguration = await GetOpenIdConfigurationAsync(bankClientProfile.IssuerUrl); new OpenBankingOpenIdConfigurationResponseValidator().Validate(openIdConfiguration) .RaiseErrorOnValidationError(); // Create claims for client reg OpenBankingClientRegistrationClaims registrationClaims = Factories.CreateRegistrationClaims( issuerUrl: bankClientProfile.IssuerUrl, sProfile: softwareStatementProfile, concatScopes: false); BankClientRegistrationClaimsOverrides registrationClaimsOverrides = bankClientProfile.BankClientRegistrationClaimsOverrides; if (!(registrationClaimsOverrides is null)) { if (!(registrationClaimsOverrides.RequestAudience is null)) { registrationClaims.Aud = registrationClaimsOverrides.RequestAudience; } } BankClientRegistrationClaims persistentRegistrationClaims = _mapper.Map <BankClientRegistrationClaims>(registrationClaims); // STEP 2 // Check for existing Open Banking client for issuer URL // If we have an Open Banking client with the same issuer URL we will check if the claims match. // If they do, we will re-use this client. // Otherwise we will return an error as only support a single client per issuer URL at present. IQueryable <BankClientProfile> clientList = await _bankClientProfileRepo .GetAsync(c => c.IssuerUrl == bankClientProfile.IssuerUrl); BankClientProfile existingClient = clientList .SingleOrDefault(); if (existingClient is object) { if (existingClient.BankClientRegistrationClaims != persistentRegistrationClaims) { throw new Exception( "There is already a client for this issuer URL but it cannot be re-used because claims are different."); } } // STEP 3 // Create new Open Banking client by posting JWT BankClientProfile client; if (existingClient is null) { JwtFactory jwtFactory = new JwtFactory(); string jwt = jwtFactory.CreateJwt( profile: softwareStatementProfile, claims: registrationClaims, useOpenBankingJwtHeaders: false); OpenBankingClientRegistrationResponse registrationResponse = await new HttpRequestBuilder() .SetMethod(HttpMethod.Post) .SetUri(openIdConfiguration.RegistrationEndpoint) .SetContent(jwt) .SetContentType("application/jwt") .Create() .RequestJsonAsync <OpenBankingClientRegistrationResponse>( client: _apiClient, requestContentIsJson: false); BankClientRegistrationData openBankingClientResponse = new BankClientRegistrationData { ClientId = registrationResponse.ClientId, ClientIdIssuedAt = registrationResponse.ClientIdIssuedAt, ClientSecret = registrationResponse.ClientSecret, ClientSecretExpiresAt = registrationResponse.ClientSecretExpiresAt }; // Create and store Open Banking client BankClientProfile newClient = _mapper.Map <BankClientProfile>(bankClientProfile); client = await PersistOpenBankingClient( value : newClient, openIdConfiguration : openIdConfiguration, registrationClaims : registrationClaims, openBankingRegistrationData : openBankingClientResponse); await _dbMultiEntityMethods.SaveChangesAsync(); } else { client = existingClient; } // Return return(new BankClientProfileResponse(client)); }
public async Task <OBWriteDomesticResponse4> CreateAsync(string consentId) { // Load relevant data objects DomesticConsent consent = await _domesticConsentRepo.GetAsync(consentId) ?? throw new KeyNotFoundException("The Consent does not exist."); ApiProfile apiProfile = await _apiProfileRepo.GetAsync(consent.ApiProfileId) ?? throw new KeyNotFoundException("The API Profile does not exist."); BankClientProfile bankClientProfile = await _openBankingClientRepo.GetAsync(apiProfile.BankClientProfileId) ?? throw new KeyNotFoundException( "The Bank Client Profile does not exist."); SoftwareStatementProfile softwareStatementProfile = _softwareStatementProfileService.GetSoftwareStatementProfile( bankClientProfile.SoftwareStatementProfileId); TokenEndpointResponse tokenEndpointResponse = _mapper.Map <TokenEndpointResponse>(consent.TokenEndpointResponse); // Create new Open Banking payment by posting JWT OBWriteDomesticConsent4 obConsent = consent.ObWriteDomesticConsent; OBWriteDomestic2 referencePayment = new OBWriteDomestic2 { Data = new OBWriteDomestic2Data { ConsentId = consent.BankId, Initiation = obConsent.Data.Initiation }, Risk = obConsent.Risk }; // Create new Open Banking payment by posting JWT OBWriteDomesticResponse4 paymentResponse; switch (apiProfile.ApiVersion) { case ApiVersion.V3P1P1: ObModels.PaymentInitiation.V3p1p1.Model.OBWriteDomestic2 newPayment = _mapper.Map <ObModels.PaymentInitiation.V3p1p1.Model.OBWriteDomestic2>(referencePayment); OBWriteDomesticResponse2 rawPaymentResponse = await PostDomesticPayment <ObModels.PaymentInitiation.V3p1p1.Model.OBWriteDomestic2, OBWriteDomesticResponse2>( payment : newPayment, apiProfile : apiProfile, softwareStatementProfile : softwareStatementProfile, bankClientProfile : bankClientProfile, tokenEndpointResponse : tokenEndpointResponse); paymentResponse = _mapper.Map <OBWriteDomesticResponse4>(rawPaymentResponse); break; case ApiVersion.V3P1P2: throw new ArgumentOutOfRangeException(); case ApiVersion.V3P1P4: paymentResponse = await PostDomesticPayment <OBWriteDomestic2, OBWriteDomesticResponse4>( payment : referencePayment, apiProfile : apiProfile, softwareStatementProfile : softwareStatementProfile, bankClientProfile : bankClientProfile, tokenEndpointResponse : tokenEndpointResponse); break; default: throw new ArgumentOutOfRangeException(); } return(paymentResponse); }
public async Task <PaymentConsentResponse> CreateAsync(DomesticPaymentConsent consent) { consent.ArgNotNull(nameof(consent)); // Load relevant objects ApiProfile apiProfile = await _apiProfileRepo.GetAsync(consent.ApiProfileId) ?? throw new KeyNotFoundException("The API Profile does not exist."); BankClientProfile bankClientProfile = await _bankClientProfileRepo.GetAsync(apiProfile.BankClientProfileId) ?? throw new KeyNotFoundException( "The Bank Client Profile does not exist."); SoftwareStatementProfile softwareStatementProfile = _softwareStatementProfileService.GetSoftwareStatementProfile( bankClientProfile.SoftwareStatementProfileId); // Get client credentials grant (we will not cache token for now but simply use to POST consent) TokenEndpointResponse tokenEndpointResponse = await PostClientCredentialsGrant(scope : "payments", client : bankClientProfile); // TODO: validate the response??? // Create new Open Banking consent by posting JWT JwtFactory jwtFactory = new JwtFactory(); OBWriteDomesticConsentResponse4 consentResponse; switch (apiProfile.ApiVersion) { case ApiVersion.V3P1P1: OBWriteDomesticConsent2 newDomesticConsent = _mapper.Map <OBWriteDomesticConsent2>(consent.DomesticConsent); OBWriteDomesticConsentResponse2 rawConsentResponse = await PostDomesticConsent <OBWriteDomesticConsent2, OBWriteDomesticConsentResponse2>( jwtFactory : jwtFactory, softwareStatementProfile : softwareStatementProfile, consent : newDomesticConsent, apiProfile : apiProfile, bankClientProfile : bankClientProfile, tokenEndpointResponse : tokenEndpointResponse); consentResponse = _mapper.Map <OBWriteDomesticConsentResponse4>(rawConsentResponse); break; case ApiVersion.V3P1P2: throw new ArgumentOutOfRangeException(); case ApiVersion.V3P1P4: consentResponse = await PostDomesticConsent <OBWriteDomesticConsent4, OBWriteDomesticConsentResponse4>( jwtFactory : jwtFactory, softwareStatementProfile : softwareStatementProfile, consent : consent.DomesticConsent, apiProfile : apiProfile, bankClientProfile : bankClientProfile, tokenEndpointResponse : tokenEndpointResponse); break; default: throw new ArgumentOutOfRangeException(); } // Generate URL for user auth string consentId = consentResponse.Data.ConsentId; string redirectUrl = softwareStatementProfile.DefaultFragmentRedirectUrl; if (redirectUrl == "") { redirectUrl = bankClientProfile.BankClientRegistrationClaims.RedirectUris[0]; } OAuth2RequestObjectClaims oAuth2RequestObjectClaims = Factories.CreateOAuth2RequestObjectClaims( openBankingClient: bankClientProfile, redirectUrl: redirectUrl, scope: new[] { "openid", "payments" }, intentId: consentId); string requestObjectJwt = jwtFactory.CreateJwt( profile: softwareStatementProfile, claims: oAuth2RequestObjectClaims, useOpenBankingJwtHeaders: false); Dictionary <string, string> keyValuePairs = new Dictionary <string, string> { { "response_type", oAuth2RequestObjectClaims.ResponseType }, { "client_id", oAuth2RequestObjectClaims.ClientId }, { "redirect_uri", oAuth2RequestObjectClaims.RedirectUri }, { "scope", oAuth2RequestObjectClaims.Scope }, { "request", requestObjectJwt }, { "nonce", oAuth2RequestObjectClaims.Nonce }, { "state", oAuth2RequestObjectClaims.State } }; string queryString = keyValuePairs.ToUrlEncoded(); string authUrl = bankClientProfile.OpenIdConfiguration.AuthorizationEndpoint + "?" + queryString; // Create and store persistent object string domesticConsentId = Guid.NewGuid().ToString(); DomesticConsent value = new DomesticConsent { State = oAuth2RequestObjectClaims.State, SoftwareStatementProfileId = bankClientProfile.SoftwareStatementProfileId, IssuerUrl = bankClientProfile.IssuerUrl, ApiProfileId = apiProfile.Id, ObWriteDomesticConsent = consent.DomesticConsent, TokenEndpointResponse = null, Id = domesticConsentId, BankId = consentId }; await _domesticConsentRepo.UpsertAsync(value); await _dbMultiEntityMethods.SaveChangesAsync(); return(new PaymentConsentResponse { AuthUrl = authUrl, ConsentId = domesticConsentId }); }