From 719edc36238b7e2161bf287f32930127ebd4f1f3 Mon Sep 17 00:00:00 2001 From: Iancu Fofiu <> Date: Thu, 26 Oct 2023 18:19:26 +0100 Subject: [PATCH 1/2] WAS-611: added support for Advanced Identity Profile Requirements --- _examples/idv/handlers.session.go | 14 + _examples/idv/models.sessionspec.go | 56 ++ _examples/idv/routes.go | 1 + _examples/idv/templates/success.html | 43 +- _examples/profile/advancedIdentityProfile.go | 65 +++ _examples/profile/main.go | 1 + _examples/profile/profile.html | 9 + consts/attribute_names.go | 33 +- docscan/session/create/session_spec.go | 40 +- docscan/session/create/session_spec_test.go | 76 +++ .../advanced_identity_profile_response.go | 10 + .../session/retrieve/get_session_result.go | 23 +- dynamic/policy_builder.go | 53 +- dynamic/policy_builder_test.go | 69 +++ profile/user_profile.go | 6 + profile/user_profile_test.go | 21 + .../AdvancedIdentityProfileReport.json | 541 ++++++++++++++++++ 17 files changed, 993 insertions(+), 68 deletions(-) create mode 100644 _examples/profile/advancedIdentityProfile.go create mode 100644 docscan/session/retrieve/advanced_identity_profile_response.go create mode 100644 test/fixtures/AdvancedIdentityProfileReport.json diff --git a/_examples/idv/handlers.session.go b/_examples/idv/handlers.session.go index 4309b693..a9e30e56 100644 --- a/_examples/idv/handlers.session.go +++ b/_examples/idv/handlers.session.go @@ -52,6 +52,20 @@ func showDBSPage(c *gin.Context) { pageFromSessionSpec(c, sessionSpec) } +func showAdvancedIdentityProfilePage(c *gin.Context) { + sessionSpec, err := buildAdvancedIdentityProfileSessionSpec() + if err != nil { + c.HTML( + http.StatusInternalServerError, + "error.html", + gin.H{ + "ErrorTitle": "Error when building sessions spec", + "ErrorMessage": err.Error()}) + return + } + pageFromSessionSpec(c, sessionSpec) +} + func pageFromSessionSpec(c *gin.Context, sessionSpec *create.SessionSpecification) { err := initialiseDocScanClient() if err != nil { diff --git a/_examples/idv/models.sessionspec.go b/_examples/idv/models.sessionspec.go index 98bbd0d2..6026e184 100644 --- a/_examples/idv/models.sessionspec.go +++ b/_examples/idv/models.sessionspec.go @@ -236,3 +236,59 @@ func buildDBSSessionSpec() (sessionSpec *create.SessionSpecification, err error) return sessionSpec, nil } + +func buildAdvancedIdentityProfileSessionSpec() (sessionSpec *create.SessionSpecification, err error) { + var sdkConfig *create.SDKConfig + sdkConfig, err = create.NewSdkConfigBuilder(). + WithAllowsCameraAndUpload(). + WithPrimaryColour("#2d9fff"). + WithSecondaryColour("#FFFFFF"). + WithFontColour("#FFFFFF"). + WithLocale("en-GB"). + WithPresetIssuingCountry("GBR"). + WithSuccessUrl("https://localhost:8080/success"). + WithErrorUrl("https://localhost:8080/error"). + WithPrivacyPolicyUrl("https://localhost:8080/privacy-policy"). + Build() + if err != nil { + return nil, err + } + + advancedIdentityProfile := []byte(`{ + "profiles": [ + { + "trust_framework": "UK_TFIDA", + "schemes": [ + { + "label": "LB912", + "type": "RTW" + } + ] + }, + { + "trust_framework": "YOTI_GLOBAL", + "schemes": [ + { + "label": "LB321", + "type": "IDENTITY", + "objective": "AL_L1" + } + ] + } + ] + }`) + + subject := []byte(`{ + "subject_id": "unique-user-id-for-examples" + }`) + + return create.NewSessionSpecificationBuilder(). + WithClientSessionTokenTTL(6000). + WithResourcesTTL(900000). + WithUserTrackingID("some-tracking-id"). + WithSDKConfig(sdkConfig). + WithAdvancedIdentityProfileRequirements(advancedIdentityProfile). + WithCreateIdentityProfilePreview(true). + WithSubject(subject). + Build() +} diff --git a/_examples/idv/routes.go b/_examples/idv/routes.go index 50c76711..9140a3d3 100644 --- a/_examples/idv/routes.go +++ b/_examples/idv/routes.go @@ -9,6 +9,7 @@ func initializeRoutes() { // Handle the index route router.GET("/", showIndexPage) router.GET("/dbs", showDBSPage) + router.GET("/advanced", showAdvancedIdentityProfilePage) router.GET("/success", showSuccessPage) router.GET("/media", getMedia) router.GET("/privacy-policy", showPrivacyPolicyPage) diff --git a/_examples/idv/templates/success.html b/_examples/idv/templates/success.html index 8c4b92c3..233c2f79 100644 --- a/_examples/idv/templates/success.html +++ b/_examples/idv/templates/success.html @@ -76,6 +76,35 @@

Identity Profile

{{ end }} {{ end }} {{ end }} + {{ if .getSessionResult.AdvancedIdentityProfileResponse }} +
+
+

Advanced Identity Profile

+
+
+ {{ $media := index .getSessionResult.AdvancedIdentityProfileResponse.Report "media"}} + {{ if $media }} + {{ $media_id := index $media "id" }} + {{ if $media_id }} +
+
+ + + + + + + +
ID + + {{ $media_id }} + +
+
+
+ {{ end }} + {{ end }} + {{ end }} {{ if .getSessionResult.IdentityProfilePreview }}
@@ -777,15 +806,15 @@

Static Liveness Resources

ID {{ $liveness.ID }} - + - +
- +

@@ -800,18 +829,18 @@

- +
- +

- +
- +
{{ end }} diff --git a/_examples/profile/advancedIdentityProfile.go b/_examples/profile/advancedIdentityProfile.go new file mode 100644 index 00000000..df735c34 --- /dev/null +++ b/_examples/profile/advancedIdentityProfile.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "fmt" + "net/http" + + "github.com/getyoti/yoti-go-sdk/v3/dynamic" +) + +func advancedIdentityProfile(w http.ResponseWriter, req *http.Request) { + advancedIdentityProfile := []byte(`{ + "profiles": [ + { + "trust_framework": "UK_TFIDA", + "schemes": [ + { + "label": "LB912", + "type": "RTW" + } + ] + }, + { + "trust_framework": "YOTI_GLOBAL", + "schemes": [ + { + "label": "LB321", + "type": "IDENTITY", + "objective": "AL_L1" + } + ] + } + ] + }`) + + policy, err := (&dynamic.PolicyBuilder{}). + WithAdvancedIdentityProfileRequirements(advancedIdentityProfile). + Build() + if err != nil { + errorPage(w, req.WithContext(context.WithValue( + req.Context(), + contextKey("yotiError"), + fmt.Sprintf(scenarioBuilderErr, err), + ))) + return + } + + subject := []byte(`{ + "subject_id": "my_subject_id" + }`) + scenario, err := (&dynamic.ScenarioBuilder{}). + WithPolicy(policy). + WithSubject(subject). + WithCallbackEndpoint(profileEndpoint).Build() + if err != nil { + errorPage(w, req.WithContext(context.WithValue( + req.Context(), + contextKey("yotiError"), + fmt.Sprintf(scenarioBuilderErr, err), + ))) + return + } + + pageFromScenario(w, req, "Advanced Identity Profile Example", scenario) +} diff --git a/_examples/profile/main.go b/_examples/profile/main.go index 11c8730d..769ddedd 100644 --- a/_examples/profile/main.go +++ b/_examples/profile/main.go @@ -72,6 +72,7 @@ func main() { http.HandleFunc("/dynamic-share", dynamicShare) http.HandleFunc("/source-constraints", sourceConstraints) http.HandleFunc("/dbs-check", dbsCheck) + http.HandleFunc("/advanced", advancedIdentityProfile) rootdir, err := os.Getwd() if err != nil { diff --git a/_examples/profile/profile.html b/_examples/profile/profile.html index 8a54cf2f..9a354858 100644 --- a/_examples/profile/profile.html +++ b/_examples/profile/profile.html @@ -55,6 +55,15 @@ {{ end }} + {{ else if eq .Prop.Name "advanced_identity_profile_report" }} + + {{ range $key, $value := .Prop.Value }} + + + + + {{ end }} +
{{ $key }}{{ jsonMarshallIndent $value }}
{{ else }} {{ .Prevalue }} {{ .Prop.Value }} diff --git a/consts/attribute_names.go b/consts/attribute_names.go index 21e3b238..fb97b43f 100644 --- a/consts/attribute_names.go +++ b/consts/attribute_names.go @@ -2,20 +2,21 @@ package consts // Attribute names for user profile attributes const ( - AttrSelfie = "selfie" - AttrGivenNames = "given_names" - AttrFamilyName = "family_name" - AttrFullName = "full_name" - AttrMobileNumber = "phone_number" - AttrEmailAddress = "email_address" - AttrDateOfBirth = "date_of_birth" - AttrAddress = "postal_address" - AttrStructuredPostalAddress = "structured_postal_address" - AttrGender = "gender" - AttrNationality = "nationality" - AttrDocumentImages = "document_images" - AttrDocumentDetails = "document_details" - AttrIdentityProfileReport = "identity_profile_report" - AttrAgeOver = "age_over:%d" - AttrAgeUnder = "age_under:%d" + AttrSelfie = "selfie" + AttrGivenNames = "given_names" + AttrFamilyName = "family_name" + AttrFullName = "full_name" + AttrMobileNumber = "phone_number" + AttrEmailAddress = "email_address" + AttrDateOfBirth = "date_of_birth" + AttrAddress = "postal_address" + AttrStructuredPostalAddress = "structured_postal_address" + AttrGender = "gender" + AttrNationality = "nationality" + AttrDocumentImages = "document_images" + AttrDocumentDetails = "document_details" + AttrIdentityProfileReport = "identity_profile_report" + AttrAdvancedIdentityProfileReport = "advanced_identity_profile_report" + AttrAgeOver = "age_over:%d" + AttrAgeUnder = "age_under:%d" ) diff --git a/docscan/session/create/session_spec.go b/docscan/session/create/session_spec.go index 0fe13c88..07e3e8e1 100644 --- a/docscan/session/create/session_spec.go +++ b/docscan/session/create/session_spec.go @@ -41,6 +41,10 @@ type SessionSpecification struct { // within the scope of a trust framework and scheme. IdentityProfileRequirements *json.RawMessage `json:"identity_profile_requirements,omitempty"` + // AdvancedIdentityProfileRequirements is a JSON object for defining a required advanced identity profile + // within the scope of specified trust frameworks and schemes. + AdvancedIdentityProfileRequirements *json.RawMessage `json:"advanced_identity_profile_requirements,omitempty"` + // CreateIdentityProfilePreview is a bool for enabling the creation of the IdentityProfilePreview CreateIdentityProfilePreview bool `json:"create_identity_profile_preview,omitempty"` @@ -54,19 +58,20 @@ type SessionSpecification struct { // SessionSpecificationBuilder builds the SessionSpecification struct type SessionSpecificationBuilder struct { - clientSessionTokenTTL int - resourcesTTL int - userTrackingID string - notifications *NotificationConfig - requestedChecks []check.RequestedCheck - requestedTasks []task.RequestedTask - sdkConfig *SDKConfig - requiredDocuments []filter.RequiredDocument - blockBiometricConsent *bool - identityProfileRequirements *json.RawMessage - createIdentityProfilePreview bool - subject *json.RawMessage - importToken *ImportToken + clientSessionTokenTTL int + resourcesTTL int + userTrackingID string + notifications *NotificationConfig + requestedChecks []check.RequestedCheck + requestedTasks []task.RequestedTask + sdkConfig *SDKConfig + requiredDocuments []filter.RequiredDocument + blockBiometricConsent *bool + identityProfileRequirements *json.RawMessage + advancedIdentityProfileRequirements *json.RawMessage + createIdentityProfilePreview bool + subject *json.RawMessage + importToken *ImportToken } // NewSessionSpecificationBuilder creates a new SessionSpecificationBuilder @@ -128,6 +133,7 @@ func (b *SessionSpecificationBuilder) WithBlockBiometricConsent(blockBiometricCo return b } +// WithCreateIdentityProfilePreview sets whether or not an Identity Profile Preview will be created. func (b *SessionSpecificationBuilder) WithCreateIdentityProfilePreview(createIdentityProfilePreview bool) *SessionSpecificationBuilder { b.createIdentityProfilePreview = createIdentityProfilePreview return b @@ -139,6 +145,13 @@ func (b *SessionSpecificationBuilder) WithIdentityProfileRequirements(identityPr return b } +// WithAdvancedIdentityProfileRequirements adds Advanced Identity Profile Requirements to the session. Must be valid JSON. +func (b *SessionSpecificationBuilder) WithAdvancedIdentityProfileRequirements(advancedIdentityProfile json.RawMessage) *SessionSpecificationBuilder { + b.advancedIdentityProfileRequirements = &advancedIdentityProfile + return b +} + +// WithSubject adds Subject to the session. Must be valid JSON. func (b *SessionSpecificationBuilder) WithSubject(subject json.RawMessage) *SessionSpecificationBuilder { b.subject = &subject return b @@ -163,6 +176,7 @@ func (b *SessionSpecificationBuilder) Build() (*SessionSpecification, error) { b.requiredDocuments, b.blockBiometricConsent, b.identityProfileRequirements, + b.advancedIdentityProfileRequirements, b.createIdentityProfilePreview, b.subject, b.importToken, diff --git a/docscan/session/create/session_spec_test.go b/docscan/session/create/session_spec_test.go index 6055af67..77e8e815 100644 --- a/docscan/session/create/session_spec_test.go +++ b/docscan/session/create/session_spec_test.go @@ -219,6 +219,82 @@ func TestExampleSessionSpecificationBuilder_Build_WithIdentityProfileRequirement } } +func ExampleSessionSpecificationBuilder_Build_withAdvancedIdentityProfileRequirements() { + advancedIdentityProfile := []byte(`{ + "profiles": [ + { + "trust_framework": "UK_TFIDA", + "schemes": [ + { + "label": "LB912", + "type": "RTW" + }, + { + "label": "LB777", + "type": "DBS", + "objective": "BASIC" + } + ] + }, + { + "trust_framework": "YOTI_GLOBAL", + "schemes": [ + { + "label": "LB321", + "type": "IDENTITY", + "objective": "AL_L1", + "config": {} + } + ] + } + ] + }`) + + sessionSpecification, err := NewSessionSpecificationBuilder(). + WithAdvancedIdentityProfileRequirements(advancedIdentityProfile). + Build() + + if err != nil { + fmt.Printf("error: %s", err.Error()) + return + } + + data, err := json.Marshal(sessionSpecification) + if err != nil { + fmt.Printf("error: %s", err.Error()) + return + } + + fmt.Println(string(data)) + // Output: {"advanced_identity_profile_requirements":{"profiles":[{"trust_framework":"UK_TFIDA","schemes":[{"label":"LB912","type":"RTW"},{"label":"LB777","type":"DBS","objective":"BASIC"}]},{"trust_framework":"YOTI_GLOBAL","schemes":[{"label":"LB321","type":"IDENTITY","objective":"AL_L1","config":{}}]}]}} +} + +func TestExampleSessionSpecificationBuilder_Build_WithAdvancedIdentityProfileRequirements_InvalidJSON(t *testing.T) { + advancedIdentityProfile := []byte(`{ + "trust_framework": UK_TFIDA", + , + }`) + + sessionSpecification, err := NewSessionSpecificationBuilder(). + WithAdvancedIdentityProfileRequirements(advancedIdentityProfile). + Build() + + if err != nil { + t.Errorf("error: %s", err.Error()) + return + } + + _, err = json.Marshal(sessionSpecification) + if err == nil { + t.Error("expected an error") + return + } + var marshallerErr *json.MarshalerError + if !errors.As(err, &marshallerErr) { + t.Errorf("wanted err to be of type '%v', got: '%v'", reflect.TypeOf(marshallerErr), reflect.TypeOf(err)) + } +} + func ExampleSessionSpecificationBuilder_Build_withSubject() { subject := []byte(`{ "subject_id": "Original subject ID" diff --git a/docscan/session/retrieve/advanced_identity_profile_response.go b/docscan/session/retrieve/advanced_identity_profile_response.go new file mode 100644 index 00000000..fe2e5de8 --- /dev/null +++ b/docscan/session/retrieve/advanced_identity_profile_response.go @@ -0,0 +1,10 @@ +package retrieve + +// AdvancedIdentityProfileResponse contains the SubjectId, the Result/FailureReasonResponse, verified identity details, +// and the verification reports that certifies how the identity was verified and how the verification levels were achieved. +type AdvancedIdentityProfileResponse struct { + SubjectId string `json:"subject_id"` + Result string `json:"result"` + FailureReasonResponse FailureReasonResponse `json:"failure_reason"` + Report map[string]interface{} `json:"identity_profile_report"` +} diff --git a/docscan/session/retrieve/get_session_result.go b/docscan/session/retrieve/get_session_result.go index 43f78c96..aef00f5d 100644 --- a/docscan/session/retrieve/get_session_result.go +++ b/docscan/session/retrieve/get_session_result.go @@ -9,17 +9,18 @@ import ( // GetSessionResult contains the information about a created session type GetSessionResult struct { - ClientSessionTokenTTL int `json:"client_session_token_ttl"` - ClientSessionToken string `json:"client_session_token"` - SessionID string `json:"session_id"` - UserTrackingID string `json:"user_tracking_id"` - State string `json:"state"` - Checks []*CheckResponse `json:"checks"` - Resources *ResourceContainer `json:"resources"` - BiometricConsentTimestamp *time.Time `json:"biometric_consent"` - IdentityProfileResponse *IdentityProfileResponse `json:"identity_profile"` - IdentityProfilePreview *IdentityProfilePreview `json:"identity_profile_preview"` - ImportTokenResponse *ImportTokenResponse `json:"import_token"` + ClientSessionTokenTTL int `json:"client_session_token_ttl"` + ClientSessionToken string `json:"client_session_token"` + SessionID string `json:"session_id"` + UserTrackingID string `json:"user_tracking_id"` + State string `json:"state"` + Checks []*CheckResponse `json:"checks"` + Resources *ResourceContainer `json:"resources"` + BiometricConsentTimestamp *time.Time `json:"biometric_consent"` + IdentityProfileResponse *IdentityProfileResponse `json:"identity_profile"` + AdvancedIdentityProfileResponse *AdvancedIdentityProfileResponse `json:"advanced_identity_profile"` + IdentityProfilePreview *IdentityProfilePreview `json:"identity_profile_preview"` + ImportTokenResponse *ImportTokenResponse `json:"import_token"` authenticityChecks []*AuthenticityCheckResponse faceMatchChecks []*FaceMatchCheckResponse textDataChecks []*TextDataCheckResponse diff --git a/dynamic/policy_builder.go b/dynamic/policy_builder.go index 7147b6b6..a97ffd18 100644 --- a/dynamic/policy_builder.go +++ b/dynamic/policy_builder.go @@ -16,19 +16,21 @@ const ( // PolicyBuilder constructs a json payload specifying the dynamic policy // for a dynamic scenario type PolicyBuilder struct { - wantedAttributes map[string]WantedAttribute - wantedAuthTypes map[int]bool - isWantedRememberMe bool - err error - identityProfileRequirements *json.RawMessage + wantedAttributes map[string]WantedAttribute + wantedAuthTypes map[int]bool + isWantedRememberMe bool + err error + identityProfileRequirements *json.RawMessage + advancedIdentityProfileRequirements *json.RawMessage } // Policy represents a dynamic policy for a share type Policy struct { - attributes []WantedAttribute - authTypes []int - rememberMeID bool - identityProfileRequirements *json.RawMessage + attributes []WantedAttribute + authTypes []int + rememberMeID bool + identityProfileRequirements *json.RawMessage + advancedIdentityProfileRequirements *json.RawMessage } // WithWantedAttribute adds an attribute from WantedAttributeBuilder to the policy @@ -206,13 +208,20 @@ func (b *PolicyBuilder) WithIdentityProfileRequirements(identityProfile json.Raw return b } +// WithAdvancedIdentityProfileRequirements adds Advanced Identity Profile Requirements to the policy. Must be valid JSON. +func (b *PolicyBuilder) WithAdvancedIdentityProfileRequirements(advancedIdentityProfile json.RawMessage) *PolicyBuilder { + b.advancedIdentityProfileRequirements = &advancedIdentityProfile + return b +} + // Build constructs a dynamic policy object func (b *PolicyBuilder) Build() (Policy, error) { return Policy{ - attributes: b.attributesAsList(), - authTypes: b.authTypesAsList(), - rememberMeID: b.isWantedRememberMe, - identityProfileRequirements: b.identityProfileRequirements, + attributes: b.attributesAsList(), + authTypes: b.authTypesAsList(), + rememberMeID: b.isWantedRememberMe, + identityProfileRequirements: b.identityProfileRequirements, + advancedIdentityProfileRequirements: b.advancedIdentityProfileRequirements, }, b.err } @@ -237,14 +246,16 @@ func (b *PolicyBuilder) authTypesAsList() []int { // MarshalJSON returns the JSON encoding func (policy *Policy) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { - Wanted []WantedAttribute `json:"wanted"` - WantedAuthTypes []int `json:"wanted_auth_types"` - WantedRememberMe bool `json:"wanted_remember_me"` - IdentityProfileRequirements *json.RawMessage `json:"identity_profile_requirements,omitempty"` + Wanted []WantedAttribute `json:"wanted"` + WantedAuthTypes []int `json:"wanted_auth_types"` + WantedRememberMe bool `json:"wanted_remember_me"` + IdentityProfileRequirements *json.RawMessage `json:"identity_profile_requirements,omitempty"` + AdvancedIdentityProfileRequirements *json.RawMessage `json:"advanced_identity_profile_requirements,omitempty"` }{ - Wanted: policy.attributes, - WantedAuthTypes: policy.authTypes, - WantedRememberMe: policy.rememberMeID, - IdentityProfileRequirements: policy.identityProfileRequirements, + Wanted: policy.attributes, + WantedAuthTypes: policy.authTypes, + WantedRememberMe: policy.rememberMeID, + IdentityProfileRequirements: policy.identityProfileRequirements, + AdvancedIdentityProfileRequirements: policy.advancedIdentityProfileRequirements, }) } diff --git a/dynamic/policy_builder_test.go b/dynamic/policy_builder_test.go index 47bce593..a902cd16 100644 --- a/dynamic/policy_builder_test.go +++ b/dynamic/policy_builder_test.go @@ -462,3 +462,72 @@ func TestPolicyBuilder_WithIdentityProfileRequirements_ShouldFailForInvalidJSON( t.Errorf("wanted err to be of type '%v', got: '%v'", reflect.TypeOf(marshallerErr), reflect.TypeOf(err)) } } + +func ExamplePolicyBuilder_WithAdvancedIdentityProfileRequirements() { + advancedIdentityProfile := []byte(`{ + "profiles": [ + { + "trust_framework": "UK_TFIDA", + "schemes": [ + { + "label": "LB912", + "type": "RTW" + }, + { + "label": "LB777", + "type": "DBS", + "objective": "BASIC" + } + ] + }, + { + "trust_framework": "YOTI_GLOBAL", + "schemes": [ + { + "label": "LB321", + "type": "IDENTITY", + "objective": "AL_L1", + "config": {} + } + ] + } + ] + }`) + + policy, err := (&PolicyBuilder{}).WithAdvancedIdentityProfileRequirements(advancedIdentityProfile).Build() + if err != nil { + fmt.Printf("error: %s", err.Error()) + return + } + + data, err := policy.MarshalJSON() + if err != nil { + fmt.Printf("error: %s", err.Error()) + return + } + + fmt.Println(string(data)) + // Output: {"wanted":[],"wanted_auth_types":[],"wanted_remember_me":false,"advanced_identity_profile_requirements":{"profiles":[{"trust_framework":"UK_TFIDA","schemes":[{"label":"LB912","type":"RTW"},{"label":"LB777","type":"DBS","objective":"BASIC"}]},{"trust_framework":"YOTI_GLOBAL","schemes":[{"label":"LB321","type":"IDENTITY","objective":"AL_L1","config":{}}]}]}} +} + +func TestPolicyBuilder_WithAdvancedIdentityProfileRequirements_ShouldFailForInvalidJSON(t *testing.T) { + advancedIdentityProfile := []byte(`{ + "trust_framework": UK_TFIDA", + , + }`) + + policy, err := (&PolicyBuilder{}).WithAdvancedIdentityProfileRequirements(advancedIdentityProfile).Build() + if err != nil { + fmt.Printf("error: %s", err.Error()) + return + } + + _, err = policy.MarshalJSON() + if err == nil { + t.Error("expected an error") + } + var marshallerErr *json.MarshalerError + if !errors.As(err, &marshallerErr) { + t.Errorf("wanted err to be of type '%v', got: '%v'", reflect.TypeOf(marshallerErr), reflect.TypeOf(err)) + } +} diff --git a/profile/user_profile.go b/profile/user_profile.go index 3ab7648d..1f59b977 100644 --- a/profile/user_profile.go +++ b/profile/user_profile.go @@ -162,6 +162,12 @@ func (p UserProfile) IdentityProfileReport() (*attribute.JSONAttribute, error) { return p.GetJSONAttribute(consts.AttrIdentityProfileReport) } +// AdvancedIdentityProfileReport represents the JSON object containing identity assertion and the +// verification reports. Will be nil if not provided by Yoti. +func (p UserProfile) AdvancedIdentityProfileReport() (*attribute.JSONAttribute, error) { + return p.GetJSONAttribute(consts.AttrAdvancedIdentityProfileReport) +} + // AgeVerifications returns a slice of age verifications for the user. // Will be an empty slice if not provided by Yoti. func (p UserProfile) AgeVerifications() (out []attribute.AgeVerification, err error) { diff --git a/profile/user_profile_test.go b/profile/user_profile_test.go index 0750fa6a..dacfa4f7 100644 --- a/profile/user_profile_test.go +++ b/profile/user_profile_test.go @@ -646,6 +646,27 @@ func TestProfile_IdentityProfileReport_RetrievesAttribute(t *testing.T) { assert.Equal(t, gotProof, "") } +func TestProfile_AdvancedIdentityProfileReport_RetrievesAttribute(t *testing.T) { + advancedIdentityProfileReportJSON, err := file.ReadFile("../test/fixtures/AdvancedIdentityProfileReport.json") + assert.NilError(t, err) + + var attr = &yotiprotoattr.Attribute{ + Name: consts.AttrAdvancedIdentityProfileReport, + Value: advancedIdentityProfileReportJSON, + ContentType: yotiprotoattr.ContentType_JSON, + Anchors: []*yotiprotoattr.Anchor{}, + } + + result := createProfileWithSingleAttribute(attr) + att, err := result.AdvancedIdentityProfileReport() + assert.NilError(t, err) + + retrievedAdvancedIdentityProfile := att.Value() + gotProof := retrievedAdvancedIdentityProfile["proof"] + + assert.Equal(t, gotProof, "") +} + func TestProfileAllowsMultipleAttributesWithSameName(t *testing.T) { firstAttribute := createStringAttribute("full_name", []byte("some_value"), []*yotiprotoattr.Anchor{}, "id") secondAttribute := createStringAttribute("full_name", []byte("some_other_value"), []*yotiprotoattr.Anchor{}, "id") diff --git a/test/fixtures/AdvancedIdentityProfileReport.json b/test/fixtures/AdvancedIdentityProfileReport.json new file mode 100644 index 00000000..160eeacc --- /dev/null +++ b/test/fixtures/AdvancedIdentityProfileReport.json @@ -0,0 +1,541 @@ +{ + "identity_assertion": { + "current_name": { + "given_names": "LAURENCE GUY", + "first_name": "LAURENCE", + "middle_name": "GUY", + "family_name": "WITHERS", + "full_name": "LAURENCE GUY WITHERS" + }, + "date_of_birth": "1981-10-05", + "current_address": { + "address": { + "address_format": 1, + "care_of": "", + "sub_building": "", + "building_number": "25", + "building": "", + "street": "25 Test Street", + "landmark": "", + "address_line1": "25 Test Street", + "address_line2": "London", + "address_line3": "EC3M 5LY", + "address_line4": "", + "address_line5": "", + "address_line6": "", + "locality": "", + "town_city": "London", + "subdistrict": "", + "district": "", + "state": "", + "postal_code": "EC3M 5LY", + "post_office": "", + "country_iso": "GBR", + "country": "United Kingdom", + "formatted_address": "25 Test Street\\nLondon\\nEC3M 5LY\\nLondon\\nEC3M 5LY\\nUnited Kingdom", + "udprn": "" + }, + "move_in": "" + } + }, + "verification_report": { + "report_id": "7fd51c9f-4131-4665-b44d-59f8aa888003", + "timestamp": "2023-10-04T11:31:15Z", + "subject_id": "ITEST", + "address_verification": { + "current_address_verified": true, + "evidence_links": [ + "5df924ad-904e-4a88-9d34-889599d29795" + ] + }, + "trust_framework": "UK_TFIDA", + "schemes_compliance": [ + { + "scheme": { + "type": "DBS", + "objective": "STANDARD", + "label": "", + "config": null + }, + "requirements_met": true, + "requirements_not_met_info": "", + "requirements_not_met_details": [] + } + ], + "assurance_process": { + "level_of_assurance": "HIGH", + "policy": "GPG45", + "procedure": "H1A", + "assurance": [ + { + "type": "EVIDENCE_STRENGTH", + "classification": "4", + "evidence_links": [ + "0c3e309e-0b14-4207-8190-109c7cf7cb78" + ] + }, + { + "type": "EVIDENCE_VALIDITY", + "classification": "3", + "evidence_links": [ + "0c3e309e-0b14-4207-8190-109c7cf7cb78" + ] + }, + { + "type": "IDENTITY_FRAUD", + "classification": "1", + "evidence_links": [ + "22542183-8363-41bf-af6e-c5e099eb26d3" + ] + }, + { + "type": "VERIFICATION", + "classification": "3", + "evidence_links": [ + "0c3e309e-0b14-4207-8190-109c7cf7cb78", + "2bc147ed-e23e-4df1-9dec-61295e04770a" + ] + } + ] + }, + "evidence": { + "face": { + "evidence_id": "2bc147ed-e23e-4df1-9dec-61295e04770a", + "initial_liveness": { + "type": "ZOOM", + "timestamp": "2023-10-04T11:31:15Z" + }, + "last_matched_liveness": { + "type": "ZOOM", + "timestamp": "2023-10-04T11:31:15Z" + }, + "verifying_org": "", + "resource_ids": [], + "check_ids": [], + "user_activity_ids": [ + "DID_TRACKING_ID" + ], + "selfie_attribute_id": "" + }, + "documents": [ + { + "evidence_id": "0c3e309e-0b14-4207-8190-109c7cf7cb78", + "timestamp": "2023-10-04T11:31:15Z", + "document_fields": { + "full_name": "LAURENCE GUY WITHERS", + "date_of_birth": "1981-10-05", + "nationality": "GBR", + "given_names": "LAURENCE GUY", + "first_name": "", + "middle_name": "", + "family_name": "WITHERS", + "place_of_birth": "", + "country_of_birth": "", + "gender": "MALE", + "name_prefix": "", + "name_suffix": "", + "first_name_alias": "", + "middle_name_alias": "", + "family_name_alias": "", + "weight": "", + "height": "", + "eye_color": "", + "structured_postal_address": null, + "document_type": "PASSPORT", + "issuing_country": "GBR", + "document_number": "546697970", + "expiration_date": "2027-05-06", + "date_of_issue": "2017-03-06", + "issuing_authority": "HMPO", + "mrz": { + "type": 2, + "line1": "P", + "authentication_report": { + "report_id": "68a952f2-d675-405c-a4fa-adea7424414b", + "timestamp": "2023-10-04T11:31:15Z", + "level": "HIGH", + "policy": "GPG44", + "trust_framework": "UK_TFIDA" + }, + "profile_match_report": null, + "verification_reports": [ + { + "report_id": "7fd51c9f-4131-4665-b44d-59f8aa888003", + "timestamp": "2023-10-04T11:31:15Z", + "subject_id": "ITEST", + "address_verification": { + "current_address_verified": true, + "evidence_links": [ + "5df924ad-904e-4a88-9d34-889599d29795" + ] + }, + "trust_framework": "UK_TFIDA", + "schemes_compliance": [ + { + "scheme": { + "type": "DBS", + "objective": "STANDARD", + "label": "", + "config": null + }, + "requirements_met": true, + "requirements_not_met_info": "", + "requirements_not_met_details": [] + } + ], + "assurance_process": { + "level_of_assurance": "HIGH", + "policy": "GPG45", + "procedure": "H1A", + "assurance": [ + { + "type": "EVIDENCE_STRENGTH", + "classification": "4", + "evidence_links": [ + "0c3e309e-0b14-4207-8190-109c7cf7cb78" + ] + }, + { + "type": "EVIDENCE_VALIDITY", + "classification": "3", + "evidence_links": [ + "0c3e309e-0b14-4207-8190-109c7cf7cb78" + ] + }, + { + "type": "IDENTITY_FRAUD", + "classification": "1", + "evidence_links": [ + "22542183-8363-41bf-af6e-c5e099eb26d3" + ] + }, + { + "type": "VERIFICATION", + "classification": "3", + "evidence_links": [ + "0c3e309e-0b14-4207-8190-109c7cf7cb78", + "2bc147ed-e23e-4df1-9dec-61295e04770a" + ] + } + ] + }, + "evidence": { + "face": { + "evidence_id": "2bc147ed-e23e-4df1-9dec-61295e04770a", + "initial_liveness": { + "type": "ZOOM", + "timestamp": "2023-10-04T11:31:15Z" + }, + "last_matched_liveness": { + "type": "ZOOM", + "timestamp": "2023-10-04T11:31:15Z" + }, + "verifying_org": "", + "resource_ids": [], + "check_ids": [], + "user_activity_ids": [ + "DID_TRACKING_ID" + ], + "selfie_attribute_id": "" + }, + "documents": [ + { + "evidence_id": "0c3e309e-0b14-4207-8190-109c7cf7cb78", + "timestamp": "2023-10-04T11:31:15Z", + "document_fields": { + "full_name": "LAURENCE GUY WITHERS", + "date_of_birth": "1981-10-05", + "nationality": "GBR", + "given_names": "LAURENCE GUY", + "first_name": "", + "middle_name": "", + "family_name": "WITHERS", + "place_of_birth": "", + "country_of_birth": "", + "gender": "MALE", + "name_prefix": "", + "name_suffix": "", + "first_name_alias": "", + "middle_name_alias": "", + "family_name_alias": "", + "weight": "", + "height": "", + "eye_color": "", + "structured_postal_address": null, + "document_type": "PASSPORT", + "issuing_country": "GBR", + "document_number": "546697970", + "expiration_date": "2027-05-06", + "date_of_issue": "2017-03-06", + "issuing_authority": "HMPO", + "mrz": { + "type": 2, + "line1": "P Date: Tue, 31 Oct 2023 15:19:02 +0000 Subject: [PATCH 2/2] WAS-611: Added advanced identity profile preview, removed advanced_identity_profile_report from share --- _examples/idv/templates/success.html | 23 ++++++++++ _examples/profile/profile.html | 9 ---- consts/attribute_names.go | 33 +++++++------ docscan/session/create/session_spec_test.go | 1 - .../advanced_identity_profile_preview.go | 7 +++ .../session/retrieve/get_session_result.go | 1 + .../retrieve/get_session_result_test.go | 46 +++++++++++++++++++ profile/user_profile.go | 6 --- profile/user_profile_test.go | 21 --------- ...sionResultWithAdvancedIdentityProfile.json | 42 +++++++++++++++++ 10 files changed, 135 insertions(+), 54 deletions(-) create mode 100644 docscan/session/retrieve/advanced_identity_profile_preview.go create mode 100644 test/fixtures/GetSessionResultWithAdvancedIdentityProfile.json diff --git a/_examples/idv/templates/success.html b/_examples/idv/templates/success.html index 233c2f79..a8e26324 100644 --- a/_examples/idv/templates/success.html +++ b/_examples/idv/templates/success.html @@ -128,6 +128,29 @@

Identity Profile Preview

{{ end }} + {{ if .getSessionResult.AdvancedIdentityProfilePreview }} +
+
+

Advanced Identity Profile Preview

+
+
+
+
+ + + + + + + +
ID + + {{ .getSessionResult.AdvancedIdentityProfilePreview.Media.ID }} + +
+
+
+ {{ end }} {{ if .getSessionResult.ImportTokenResponse }}
diff --git a/_examples/profile/profile.html b/_examples/profile/profile.html index 9a354858..8a54cf2f 100644 --- a/_examples/profile/profile.html +++ b/_examples/profile/profile.html @@ -55,15 +55,6 @@ {{ end }} - {{ else if eq .Prop.Name "advanced_identity_profile_report" }} - - {{ range $key, $value := .Prop.Value }} - - - - - {{ end }} -
{{ $key }}{{ jsonMarshallIndent $value }}
{{ else }} {{ .Prevalue }} {{ .Prop.Value }} diff --git a/consts/attribute_names.go b/consts/attribute_names.go index fb97b43f..21e3b238 100644 --- a/consts/attribute_names.go +++ b/consts/attribute_names.go @@ -2,21 +2,20 @@ package consts // Attribute names for user profile attributes const ( - AttrSelfie = "selfie" - AttrGivenNames = "given_names" - AttrFamilyName = "family_name" - AttrFullName = "full_name" - AttrMobileNumber = "phone_number" - AttrEmailAddress = "email_address" - AttrDateOfBirth = "date_of_birth" - AttrAddress = "postal_address" - AttrStructuredPostalAddress = "structured_postal_address" - AttrGender = "gender" - AttrNationality = "nationality" - AttrDocumentImages = "document_images" - AttrDocumentDetails = "document_details" - AttrIdentityProfileReport = "identity_profile_report" - AttrAdvancedIdentityProfileReport = "advanced_identity_profile_report" - AttrAgeOver = "age_over:%d" - AttrAgeUnder = "age_under:%d" + AttrSelfie = "selfie" + AttrGivenNames = "given_names" + AttrFamilyName = "family_name" + AttrFullName = "full_name" + AttrMobileNumber = "phone_number" + AttrEmailAddress = "email_address" + AttrDateOfBirth = "date_of_birth" + AttrAddress = "postal_address" + AttrStructuredPostalAddress = "structured_postal_address" + AttrGender = "gender" + AttrNationality = "nationality" + AttrDocumentImages = "document_images" + AttrDocumentDetails = "document_details" + AttrIdentityProfileReport = "identity_profile_report" + AttrAgeOver = "age_over:%d" + AttrAgeUnder = "age_under:%d" ) diff --git a/docscan/session/create/session_spec_test.go b/docscan/session/create/session_spec_test.go index 77e8e815..80633bde 100644 --- a/docscan/session/create/session_spec_test.go +++ b/docscan/session/create/session_spec_test.go @@ -253,7 +253,6 @@ func ExampleSessionSpecificationBuilder_Build_withAdvancedIdentityProfileRequire sessionSpecification, err := NewSessionSpecificationBuilder(). WithAdvancedIdentityProfileRequirements(advancedIdentityProfile). Build() - if err != nil { fmt.Printf("error: %s", err.Error()) return diff --git a/docscan/session/retrieve/advanced_identity_profile_preview.go b/docscan/session/retrieve/advanced_identity_profile_preview.go new file mode 100644 index 00000000..4e6e36f8 --- /dev/null +++ b/docscan/session/retrieve/advanced_identity_profile_preview.go @@ -0,0 +1,7 @@ +package retrieve + +// AdvancedIdentityProfilePreview contains info about the media needed to +// retrieve the Advanced Identity Profile Preview. +type AdvancedIdentityProfilePreview struct { + Media *MediaResponse `json:"media"` +} diff --git a/docscan/session/retrieve/get_session_result.go b/docscan/session/retrieve/get_session_result.go index aef00f5d..cc5ff4e5 100644 --- a/docscan/session/retrieve/get_session_result.go +++ b/docscan/session/retrieve/get_session_result.go @@ -20,6 +20,7 @@ type GetSessionResult struct { IdentityProfileResponse *IdentityProfileResponse `json:"identity_profile"` AdvancedIdentityProfileResponse *AdvancedIdentityProfileResponse `json:"advanced_identity_profile"` IdentityProfilePreview *IdentityProfilePreview `json:"identity_profile_preview"` + AdvancedIdentityProfilePreview *AdvancedIdentityProfilePreview `json:"advanced_identity_profile_preview"` ImportTokenResponse *ImportTokenResponse `json:"import_token"` authenticityChecks []*AuthenticityCheckResponse faceMatchChecks []*FaceMatchCheckResponse diff --git a/docscan/session/retrieve/get_session_result_test.go b/docscan/session/retrieve/get_session_result_test.go index 3037b4cf..cf02c763 100644 --- a/docscan/session/retrieve/get_session_result_test.go +++ b/docscan/session/retrieve/get_session_result_test.go @@ -215,6 +215,36 @@ func TestGetSessionResult_UnmarshalJSON_IdentityProfile(t *testing.T) { assert.Equal(t, mid, "c69ff2db-6caf-4e74-8386-037711bbc8d7") } +func TestGetSessionResult_UnmarshalJSON_AdvancedIdentityProfile(t *testing.T) { + bytes, err := file.ReadFile("../../../test/fixtures/GetSessionResultWithAdvancedIdentityProfile.json") + assert.NilError(t, err) + + var result retrieve.GetSessionResult + err = result.UnmarshalJSON(bytes) + assert.NilError(t, err) + + identityProfile := result.AdvancedIdentityProfileResponse + assert.Assert(t, identityProfile != nil) + + assert.Equal(t, identityProfile.SubjectId, "someStringHere") + assert.Equal(t, identityProfile.Result, "DONE") + assert.Equal(t, identityProfile.FailureReasonResponse, retrieve.FailureReasonResponse{ReasonCode: "MANDATORY_DOCUMENT_COULD_NOT_BE_PROVIDED"}) + + compliances, ok := identityProfile.Report["compliance"].([]interface{}) + assert.Equal(t, ok, true) + assert.Equal(t, len(compliances), 1) + + compliance, ok := compliances[0].(map[string]interface{}) + assert.Equal(t, ok, true) + assert.Equal(t, compliance["trust_framework"], "UK_TFIDA") + + media, ok := identityProfile.Report["media"].(map[string]interface{}) + assert.Equal(t, ok, true) + mid, ok := media["id"].(string) + assert.Equal(t, ok, true) + assert.Equal(t, mid, "c69ff2db-6caf-4e74-8386-037711bbc8d7") +} + func TestGetSessionResult_UnmarshalJSON_IdentityProfilePreview(t *testing.T) { bytes, err := file.ReadFile("../../../test/fixtures/GetSessionResultWithIdentityProfile.json") assert.NilError(t, err) @@ -230,3 +260,19 @@ func TestGetSessionResult_UnmarshalJSON_IdentityProfilePreview(t *testing.T) { assert.Equal(t, identityProfilePreview.Media.ID, "3fa85f64-5717-4562-b3fc-2c963f66afa6") assert.Equal(t, identityProfilePreview.Media.Type, "IMAGE") } + +func TestGetSessionResult_UnmarshalJSON_AdvancedIdentityProfilePreview(t *testing.T) { + bytes, err := file.ReadFile("../../../test/fixtures/GetSessionResultWithAdvancedIdentityProfile.json") + assert.NilError(t, err) + + var result retrieve.GetSessionResult + err = result.UnmarshalJSON(bytes) + assert.NilError(t, err) + + identityProfilePreview := result.AdvancedIdentityProfilePreview + assert.Assert(t, identityProfilePreview != nil) + + assert.Assert(t, identityProfilePreview.Media != nil) + assert.Equal(t, identityProfilePreview.Media.ID, "3fa85f64-5717-4562-b3fc-2c963f66afa6") + assert.Equal(t, identityProfilePreview.Media.Type, "IMAGE") +} diff --git a/profile/user_profile.go b/profile/user_profile.go index 1f59b977..3ab7648d 100644 --- a/profile/user_profile.go +++ b/profile/user_profile.go @@ -162,12 +162,6 @@ func (p UserProfile) IdentityProfileReport() (*attribute.JSONAttribute, error) { return p.GetJSONAttribute(consts.AttrIdentityProfileReport) } -// AdvancedIdentityProfileReport represents the JSON object containing identity assertion and the -// verification reports. Will be nil if not provided by Yoti. -func (p UserProfile) AdvancedIdentityProfileReport() (*attribute.JSONAttribute, error) { - return p.GetJSONAttribute(consts.AttrAdvancedIdentityProfileReport) -} - // AgeVerifications returns a slice of age verifications for the user. // Will be an empty slice if not provided by Yoti. func (p UserProfile) AgeVerifications() (out []attribute.AgeVerification, err error) { diff --git a/profile/user_profile_test.go b/profile/user_profile_test.go index dacfa4f7..0750fa6a 100644 --- a/profile/user_profile_test.go +++ b/profile/user_profile_test.go @@ -646,27 +646,6 @@ func TestProfile_IdentityProfileReport_RetrievesAttribute(t *testing.T) { assert.Equal(t, gotProof, "") } -func TestProfile_AdvancedIdentityProfileReport_RetrievesAttribute(t *testing.T) { - advancedIdentityProfileReportJSON, err := file.ReadFile("../test/fixtures/AdvancedIdentityProfileReport.json") - assert.NilError(t, err) - - var attr = &yotiprotoattr.Attribute{ - Name: consts.AttrAdvancedIdentityProfileReport, - Value: advancedIdentityProfileReportJSON, - ContentType: yotiprotoattr.ContentType_JSON, - Anchors: []*yotiprotoattr.Anchor{}, - } - - result := createProfileWithSingleAttribute(attr) - att, err := result.AdvancedIdentityProfileReport() - assert.NilError(t, err) - - retrievedAdvancedIdentityProfile := att.Value() - gotProof := retrievedAdvancedIdentityProfile["proof"] - - assert.Equal(t, gotProof, "") -} - func TestProfileAllowsMultipleAttributesWithSameName(t *testing.T) { firstAttribute := createStringAttribute("full_name", []byte("some_value"), []*yotiprotoattr.Anchor{}, "id") secondAttribute := createStringAttribute("full_name", []byte("some_other_value"), []*yotiprotoattr.Anchor{}, "id") diff --git a/test/fixtures/GetSessionResultWithAdvancedIdentityProfile.json b/test/fixtures/GetSessionResultWithAdvancedIdentityProfile.json new file mode 100644 index 00000000..d1c85372 --- /dev/null +++ b/test/fixtures/GetSessionResultWithAdvancedIdentityProfile.json @@ -0,0 +1,42 @@ +{ + "session_id": "a1746488-efcc-4c59-bd28-f849dcb933a2", + "client_session_token_ttl": 599, + "user_tracking_id": "user-tracking-id", + "biometric_consent": "2022-03-29T11:39:08.473Z", + "state": "COMPLETED", + "client_session_token": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "advanced_identity_profile": { + "subject_id": "someStringHere", + "result": "DONE", + "failure_reason": { + "reason_code": "MANDATORY_DOCUMENT_COULD_NOT_BE_PROVIDED" + }, + "identity_profile_report": { + "compliance": [{ + "trust_framework": "UK_TFIDA", + "schemes_compliance": [{ + "scheme": { + "type": "DBS", + "objective": "STANDARD" + }, + "requirements_met": true, + "requirements_not_met_info": "some string here" + }] + }], + "media": { + "id": "c69ff2db-6caf-4e74-8386-037711bbc8d7", + "type": "IMAGE", + "created": "2022-03-29T11:39:24Z", + "last_updated": "2022-03-29T11:39:24Z" + } + } + }, + "advanced_identity_profile_preview": { + "media": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "type": "IMAGE", + "created": "2021-06-11T11:39:24Z", + "last_updated": "2021-06-11T11:39:24Z" + } + } +}