diff --git a/.gitignore b/.gitignore index b8687077..b79bd93d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ examples/profile/images/YotiSelfie.jpeg # Example project generated self-signed certificate examples/profile/yotiSelfSignedCert.pem examples/profile/yotiSelfSignedKey.pem + +# Debug files +debug \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0551b633..ccae2763 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ For developing in VS Code, use the following `launch.json` file (placed inside a "host": "127.0.0.1", "program": "${workspaceFolder}/examples/profile/main.go", "env": {}, - "args": [], + "args": ["certificatehelper.go"], "showLog": true } ] diff --git a/README.md b/README.md index 379788ac..df8a6212 100644 --- a/README.md +++ b/README.md @@ -104,37 +104,74 @@ if len(errStrings) != 0 { } ``` +### Profile + You can then get the user profile from the activityDetails struct: ```Go -rememberMeID := activityDetails.RememberMeID -userProfile := activityDetails.UserProfile - -selfie := userProfile.Selfie().Value -givenNames := userProfile.GivenNames().Value -familyName := userProfile.FamilyName().Value -fullName := userProfile.FullName().Value -mobileNumber := userProfile.MobileNumber().Value -emailAddress := userProfile.EmailAddress().Value -address := userProfile.Address().Value -gender := userProfile.Gender().Value -nationality := userProfile.Nationality().Value -dob, err := userProfile.DateOfBirth() +var rememberMeID string = activityDetails.RememberMeID +var userProfile yoti.Profile = activityDetails.UserProfile + +var selfie = userProfile.Selfie().Value() +var givenNames string = userProfile.GivenNames().Value() +var familyName string = userProfile.FamilyName().Value() +var fullName string = userProfile.FullName().Value() +var mobileNumber string = userProfile.MobileNumber().Value() +var emailAddress string = userProfile.EmailAddress().Value() +var address string = userProfile.Address().Value() +var gender string = userProfile.Gender().Value() +var nationality string = userProfile.Nationality().Value() +var dateOfBirth *time.Time +dobAttr, err := userProfile.DateOfBirth() if err != nil { - //handle error + //handle error } else { - dateofBirth = dob + dateOfBirth = dobAttr.Value() } ``` If you have chosen Verify Condition on the Yoti Dashboard with the age condition of "Over 18", you can retrieve the user information with the generic .GetAttribute method, which requires the result to be cast to the original type: ```Go -userProfile.GetAttribute("age_over:18").Value.(string) +userProfile.GetAttribute("age_over:18").Value().(string) ``` GetAttribute returns an interface, the value can be acquired through a type assertion. +### Anchors, Sources and Verifiers + +An `Anchor` represents how a given Attribute has been _sourced_ or _verified_. These values are created and signed whenever a Profile Attribute is created, or verified with an external party. + +For example, an attribute value that was _sourced_ from a Passport might have the following values: + +`Anchor` property | Example value +-----|------ +type | SOURCE +value | PASSPORT +subType | OCR +signedTimestamp | 2017-10-31, 19:45:59.123789 + +Similarly, an attribute _verified_ against the data held by an external party will have an `Anchor` of type _VERIFIER_, naming the party that verified it. + +From each attribute can retrieve the `Anchors`, and subsets `Sources` and `Verifiers` (all as `[]*anchor.Anchor`) as follows: + +```Go +givenNamesAnchors := userProfile.GivenNames().Anchors() +givenNamesSources := userProfile.GivenNames().Sources() +givenNamesVerifiers := userProfile.GivenNames().Verifiers() +``` + +You can also retrieve further properties from these respective anchors in the following way: + +```Go +var givenNamesFirstAnchor *anchor.Anchor = userProfile.GivenNames().Anchors()[0] + +var anchorType anchor.Type = givenNamesFirstAnchor.Type +var signedTimestamp *time.Time = givenNamesFirstAnchor.SignedTimestamp().Timestamp +var subType string = givenNamesFirstAnchor.SubType() +var value []string = givenNamesFirstAnchor.Value() +``` + ## Handling Users When you retrieve the user profile, you receive a user ID generated by Yoti exclusively for your application. @@ -200,7 +237,7 @@ Performing an AML check on a person *requires* their consent. Given a YotiClient initialised with your SDK ID and KeyPair (see [Client Initialisation](#client-initialisation)) performing an AML check is a straightforward case of providing basic profile data. -```go +```Go givenNames := "Edward Richard George" familyName := "Heath" diff --git a/anchor/anchors.go b/anchor/anchors.go index e73cb892..0b0ad62d 100644 --- a/anchor/anchors.go +++ b/anchor/anchors.go @@ -57,8 +57,8 @@ func (a Anchor) OriginServerCerts() []*x509.Certificate { // message associated with the timestamp is the marshaled form of // AttributeSigning (i.e. the same message that is signed in the // Signature field). This method returns the SignedTimestamp -// object, the actual timestamp as a DateTime can be called with -// .Timestamp() on this object +// object, the actual timestamp as a *time.Time can be called with +// .Timestamp on the result of this function func (a Anchor) SignedTimestamp() SignedTimestamp { return a.signedTimestamp } @@ -76,3 +76,22 @@ func (a Anchor) SubType() string { func (a Anchor) Value() []string { return a.value } + +// GetSources returns the anchors which identify how and when an attribute value was acquired. +func GetSources(anchors []*Anchor) (sources []*Anchor) { + return filterAnchors(anchors, AnchorTypeSource) +} + +// GetVerifiers returns the anchors which identify how and when an attribute value was verified by another provider. +func GetVerifiers(anchors []*Anchor) (sources []*Anchor) { + return filterAnchors(anchors, AnchorTypeVerifier) +} + +func filterAnchors(anchors []*Anchor, anchorType Type) (result []*Anchor) { + for _, v := range anchors { + if v.Type == anchorType { + result = append(result, v) + } + } + return result +} diff --git a/attribute/genericattribute.go b/attribute/genericattribute.go index 9caa27b7..245d8641 100644 --- a/attribute/genericattribute.go +++ b/attribute/genericattribute.go @@ -11,8 +11,10 @@ import ( //GenericAttribute is a Yoti attribute which returns a generic value type GenericAttribute struct { *yotiprotoattr_v3.Attribute - Value interface{} - Anchors []*anchor.Anchor + value interface{} + anchors []*anchor.Anchor + sources []*anchor.Anchor + verifiers []*anchor.Anchor } //NewGeneric creates a new generic attribute @@ -49,12 +51,38 @@ func NewGeneric(a *yotiprotoattr_v3.Attribute) *GenericAttribute { value = a.Value } + parsedAnchors := anchor.ParseAnchors(a.Anchors) + return &GenericAttribute{ Attribute: &yotiprotoattr_v3.Attribute{ Name: a.Name, ContentType: a.ContentType, }, - Value: value, - Anchors: anchor.ParseAnchors(a.Anchors), + value: value, + anchors: parsedAnchors, + sources: anchor.GetSources(parsedAnchors), + verifiers: anchor.GetVerifiers(parsedAnchors), } } + +// Value returns the value of the GenericAttribute as an interface +func (a *GenericAttribute) Value() interface{} { + return a.value +} + +// Anchors are the metadata associated with an attribute. They describe +// how an attribute has been provided to Yoti (SOURCE Anchor) and how +// it has been verified (VERIFIER Anchor). +func (a *GenericAttribute) Anchors() []*anchor.Anchor { + return a.anchors +} + +// Sources returns the anchors which identify how and when an attribute value was acquired. +func (a *GenericAttribute) Sources() []*anchor.Anchor { + return a.sources +} + +// Verifiers returns the anchors which identify how and when an attribute value was verified by another provider. +func (a *GenericAttribute) Verifiers() []*anchor.Anchor { + return a.verifiers +} diff --git a/attribute/image.go b/attribute/image.go index 8ff1b9c8..e615e411 100644 --- a/attribute/image.go +++ b/attribute/image.go @@ -2,41 +2,26 @@ package attribute import ( "encoding/base64" - "log" + "fmt" ) -//ImageType Image format -type ImageType int - const ( //ImageTypeJpeg JPEG format - ImageTypeJpeg ImageType = 1 + iota + ImageTypeJpeg string = "jpeg" //ImageTypePng PNG format - ImageTypePng - //ImageTypeOther Other image formats - ImageTypeOther + ImageTypePng string = "png" ) //Image format of the image and the image data type Image struct { - Type ImageType + Type string Data []byte } -var mimeTypeMap = map[ImageType]string{ - ImageTypeJpeg: "image/jpeg", - ImageTypePng: "image/png", -} - // GetMIMEType returns the MIME type of this piece of Yoti user information. For more information see: // https://en.wikipedia.org/wiki/Media_type -func GetMIMEType(imageType ImageType) (result string) { - if val, ok := mimeTypeMap[imageType]; ok { - return val - } - - log.Printf("Unable to find a matching MIME type for value type %q", imageType) - return +func GetMIMEType(imageType string) string { + return fmt.Sprintf("image/%v", imageType) } // Base64URL is the Image encoded as a base64 URL diff --git a/attribute/imageattribute.go b/attribute/imageattribute.go index 701d03a4..359febb7 100644 --- a/attribute/imageattribute.go +++ b/attribute/imageattribute.go @@ -1,6 +1,8 @@ package attribute import ( + "errors" + "github.com/getyoti/yoti-go-sdk/anchor" "github.com/getyoti/yoti-go-sdk/yotiprotoattr_v3" ) @@ -8,13 +10,15 @@ import ( //ImageAttribute is a Yoti attribute which returns an image as its value type ImageAttribute struct { *yotiprotoattr_v3.Attribute - Value *Image - Anchors []*anchor.Anchor + value *Image + anchors []*anchor.Anchor + sources []*anchor.Anchor + verifiers []*anchor.Anchor } //NewImage creates a new Image attribute -func NewImage(a *yotiprotoattr_v3.Attribute) *ImageAttribute { - var imageType ImageType +func NewImage(a *yotiprotoattr_v3.Attribute) (*ImageAttribute, error) { + var imageType string switch a.ContentType { case yotiprotoattr_v3.ContentType_JPEG: @@ -24,18 +28,44 @@ func NewImage(a *yotiprotoattr_v3.Attribute) *ImageAttribute { imageType = ImageTypePng default: - imageType = ImageTypeOther + return nil, errors.New("Cannot create ImageAttribute with unsupported type") } + parsedAnchors := anchor.ParseAnchors(a.Anchors) + return &ImageAttribute{ Attribute: &yotiprotoattr_v3.Attribute{ Name: a.Name, ContentType: a.ContentType, }, - Value: &Image{ + value: &Image{ Data: a.Value, Type: imageType, }, - Anchors: anchor.ParseAnchors(a.Anchors), - } + anchors: parsedAnchors, + sources: anchor.GetSources(parsedAnchors), + verifiers: anchor.GetVerifiers(parsedAnchors), + }, nil +} + +// Value returns the value of the ImageAttribute as *Image +func (a *ImageAttribute) Value() *Image { + return a.value +} + +// Anchors are the metadata associated with an attribute. They describe +// how an attribute has been provided to Yoti (SOURCE Anchor) and how +// it has been verified (VERIFIER Anchor). +func (a *ImageAttribute) Anchors() []*anchor.Anchor { + return a.anchors +} + +// Sources returns the anchors which identify how and when an attribute value was acquired. +func (a *ImageAttribute) Sources() []*anchor.Anchor { + return a.sources +} + +// Verifiers returns the anchors which identify how and when an attribute value was verified by another provider. +func (a *ImageAttribute) Verifiers() []*anchor.Anchor { + return a.verifiers } diff --git a/attribute/jsonattribute.go b/attribute/jsonattribute.go index 3685fbe3..ef729289 100644 --- a/attribute/jsonattribute.go +++ b/attribute/jsonattribute.go @@ -11,8 +11,10 @@ import ( //JSONAttribute is a Yoti attribute which returns an interface as its value type JSONAttribute struct { *yotiprotoattr_v3.Attribute // Value returns the value of a JSON attribute in the form of an interface - Value interface{} - Anchors []*anchor.Anchor + value interface{} + anchors []*anchor.Anchor + sources []*anchor.Anchor + verifiers []*anchor.Anchor } //NewJSON creates a new JSON attribute @@ -23,13 +25,17 @@ func NewJSON(a *yotiprotoattr_v3.Attribute) (*JSONAttribute, error) { return nil, err } + parsedAnchors := anchor.ParseAnchors(a.Anchors) + return &JSONAttribute{ Attribute: &yotiprotoattr_v3.Attribute{ Name: a.Name, ContentType: a.ContentType, }, - Value: interfaceValue, - Anchors: anchor.ParseAnchors(a.Anchors), + value: interfaceValue, + anchors: parsedAnchors, + sources: anchor.GetSources(parsedAnchors), + verifiers: anchor.GetVerifiers(parsedAnchors), }, nil } @@ -44,3 +50,25 @@ func UnmarshallJSON(byteValue []byte) (result interface{}, err error) { return unmarshalledJSON, err } + +// Value returns the value of the JSONAttribute as an interface +func (a *JSONAttribute) Value() interface{} { + return a.value +} + +// Anchors are the metadata associated with an attribute. They describe +// how an attribute has been provided to Yoti (SOURCE Anchor) and how +// it has been verified (VERIFIER Anchor). +func (a *JSONAttribute) Anchors() []*anchor.Anchor { + return a.anchors +} + +// Sources returns the anchors which identify how and when an attribute value was acquired. +func (a *JSONAttribute) Sources() []*anchor.Anchor { + return a.sources +} + +// Verifiers returns the anchors which identify how and when an attribute value was verified by another provider. +func (a *JSONAttribute) Verifiers() []*anchor.Anchor { + return a.verifiers +} diff --git a/attribute/stringattribute.go b/attribute/stringattribute.go index e128a031..abbfbdc7 100644 --- a/attribute/stringattribute.go +++ b/attribute/stringattribute.go @@ -8,18 +8,46 @@ import ( //StringAttribute is a Yoti attribute which returns a string as its value type StringAttribute struct { *yotiprotoattr_v3.Attribute - Value string - Anchors []*anchor.Anchor + value string + anchors []*anchor.Anchor + sources []*anchor.Anchor + verifiers []*anchor.Anchor } //NewString creates a new String attribute func NewString(a *yotiprotoattr_v3.Attribute) *StringAttribute { + parsedAnchors := anchor.ParseAnchors(a.Anchors) + return &StringAttribute{ Attribute: &yotiprotoattr_v3.Attribute{ Name: a.Name, ContentType: a.ContentType, }, - Value: string(a.Value), - Anchors: anchor.ParseAnchors(a.Anchors), + value: string(a.Value), + anchors: parsedAnchors, + sources: anchor.GetSources(parsedAnchors), + verifiers: anchor.GetVerifiers(parsedAnchors), } } + +// Value returns the value of the StringAttribute as a string +func (a *StringAttribute) Value() string { + return a.value +} + +// Anchors are the metadata associated with an attribute. They describe +// how an attribute has been provided to Yoti (SOURCE Anchor) and how +// it has been verified (VERIFIER Anchor). +func (a *StringAttribute) Anchors() []*anchor.Anchor { + return a.anchors +} + +// Sources returns the anchors which identify how and when an attribute value was acquired. +func (a *StringAttribute) Sources() []*anchor.Anchor { + return a.sources +} + +// Verifiers returns the anchors which identify how and when an attribute value was verified by another provider. +func (a *StringAttribute) Verifiers() []*anchor.Anchor { + return a.verifiers +} diff --git a/attribute/timeattribute.go b/attribute/timeattribute.go index 40b2bada..5487fe73 100644 --- a/attribute/timeattribute.go +++ b/attribute/timeattribute.go @@ -11,8 +11,10 @@ import ( //TimeAttribute is a Yoti attribute which returns a time as its value type TimeAttribute struct { *yotiprotoattr_v3.Attribute - Value *time.Time - Anchors []*anchor.Anchor + value *time.Time + anchors []*anchor.Anchor + sources []*anchor.Anchor + verifiers []*anchor.Anchor } //NewTime creates a new Time attribute @@ -24,12 +26,38 @@ func NewTime(a *yotiprotoattr_v3.Attribute) (*TimeAttribute, error) { return nil, err } + parsedAnchors := anchor.ParseAnchors(a.Anchors) + return &TimeAttribute{ Attribute: &yotiprotoattr_v3.Attribute{ Name: a.Name, ContentType: a.ContentType, }, - Value: &parsedTime, - Anchors: anchor.ParseAnchors(a.Anchors), + value: &parsedTime, + anchors: parsedAnchors, + sources: anchor.GetSources(parsedAnchors), + verifiers: anchor.GetVerifiers(parsedAnchors), }, nil } + +// Value returns the value of the TimeAttribute as *time.Time +func (a *TimeAttribute) Value() *time.Time { + return a.value +} + +// Anchors are the metadata associated with an attribute. They describe +// how an attribute has been provided to Yoti (SOURCE Anchor) and how +// it has been verified (VERIFIER Anchor). +func (a *TimeAttribute) Anchors() []*anchor.Anchor { + return a.anchors +} + +// Sources returns the anchors which identify how and when an attribute value was acquired. +func (a *TimeAttribute) Sources() []*anchor.Anchor { + return a.sources +} + +// Verifiers returns the anchors which identify how and when an attribute value was verified by another provider. +func (a *TimeAttribute) Verifiers() []*anchor.Anchor { + return a.verifiers +} diff --git a/examples/aml/main.go b/examples/aml/main.go index 09177b03..6e1d1776 100644 --- a/examples/aml/main.go +++ b/examples/aml/main.go @@ -18,15 +18,16 @@ var ( ) func main() { - var sdkID = os.Getenv("YOTI_CLIENT_SDK_ID") - var key, err = ioutil.ReadFile(os.Getenv("YOTI_KEY_FILE_PATH")) + var err error + key, err = ioutil.ReadFile(os.Getenv("YOTI_KEY_FILE_PATH")) + sdkID = os.Getenv("YOTI_CLIENT_SDK_ID") if err != nil { log.Printf("Unable to retrieve `YOTI_KEY_FILE_PATH`. Error: `%s`", err) return } - var client = yoti.Client{ + client = &yoti.Client{ SdkID: sdkID, Key: key} diff --git a/examples/profile/.env.example b/examples/profile/.env.example index 44d5459d..68688cef 100644 --- a/examples/profile/.env.example +++ b/examples/profile/.env.example @@ -1,3 +1,4 @@ +YOTI_SCENARIO_ID= YOTI_APPLICATION_ID= YOTI_CLIENT_SDK_ID= YOTI_KEY_FILE_PATH= diff --git a/examples/profile/login.html b/examples/profile/login.html index 0e689c8a..ae22c311 100644 --- a/examples/profile/login.html +++ b/examples/profile/login.html @@ -3,10 +3,11 @@ Yoti Example Project - + - Use Yoti + Use Yoti diff --git a/examples/profile/main.go b/examples/profile/main.go index 47464450..671bd7c4 100644 --- a/examples/profile/main.go +++ b/examples/profile/main.go @@ -6,6 +6,7 @@ import ( "html/template" "image" "image/jpeg" + "io" "io/ioutil" "log" "net/http" @@ -20,6 +21,7 @@ import ( var ( sdkID string + scenarioID string key []byte client *yoti.Client selfSignedCertName = "yotiSelfSignedCert.pem" @@ -29,6 +31,7 @@ var ( func home(w http.ResponseWriter, req *http.Request) { templateVars := map[string]interface{}{ + "yotiScenarioID": os.Getenv("YOTI_SCENARIO_ID"), "yotiApplicationID": os.Getenv("YOTI_APPLICATION_ID")} t, _ := template.ParseFiles("login.html") @@ -36,40 +39,61 @@ func home(w http.ResponseWriter, req *http.Request) { } func profile(w http.ResponseWriter, r *http.Request) { - var sdkID = os.Getenv("YOTI_CLIENT_SDK_ID") - var key, err = ioutil.ReadFile(os.Getenv("YOTI_KEY_FILE_PATH")) + var err error + key, err = ioutil.ReadFile(os.Getenv("YOTI_KEY_FILE_PATH")) + sdkID = os.Getenv("YOTI_CLIENT_SDK_ID") if err != nil { - log.Printf("Unable to retrieve `YOTI_KEY_FILE_PATH`. Error: `%s`", err) - return + log.Fatalf("Unable to retrieve `YOTI_KEY_FILE_PATH`. Error: `%s`", err) } - var client = yoti.Client{ + client = &yoti.Client{ SdkID: sdkID, Key: key} yotiOneTimeUseToken := r.URL.Query().Get("token") - profile, err := client.GetUserProfile(yotiOneTimeUseToken) - if err == nil { - templateVars := map[string]interface{}{ - "profile": profile, - "selfieBase64URL": template.URL(profile.Selfie.URL())} + activityDetails, errStrings := client.GetActivityDetails(yotiOneTimeUseToken) + if len(errStrings) != 0 { + log.Fatalf("Errors: %v", errStrings) + } - decodedImage := decodeImage(profile.Selfie.Data) - file := createImage() - saveImage(decodedImage, file) + userProfile := activityDetails.UserProfile + + selfie := userProfile.Selfie() + var base64URL string + if selfie != nil { + base64URL, err = selfie.Value().Base64URL() - t, err := template.ParseFiles("profile.html") if err != nil { - fmt.Println(err) - return + log.Fatalf("Unable to retrieve get Base64 URL of selfie. Error: %q", err) } - t.Execute(w, templateVars) - } else { + decodedImage := decodeImage(selfie.Value().Data) + file := createImage() + saveImage(decodedImage, file) + } + + dob, err := userProfile.DateOfBirth() + if err != nil { + log.Fatalf("Error parsing Date of Birth attribute. Error %q", err) + } + + templateVars := map[string]interface{}{ + "profile": userProfile, + "selfieBase64URL": template.URL(base64URL), + "rememberMeID": activityDetails.RememberMeID, + "dateOfBirth": dob, + } + + var t *template.Template + t, err = template.ParseFiles("profile.html") + if err != nil { fmt.Println(err) + return } + + t.Execute(w, templateVars) } func main() { @@ -131,7 +155,7 @@ func createImage() (file *os.File) { return } -func saveImage(img image.Image, file *os.File) { +func saveImage(img image.Image, file io.Writer) { var opt jpeg.Options opt.Quality = 100 diff --git a/examples/profile/profile.html b/examples/profile/profile.html index e45fa604..be93768d 100644 --- a/examples/profile/profile.html +++ b/examples/profile/profile.html @@ -8,8 +8,8 @@

Home

- - + + {{if .selfieBase64URL}} @@ -24,61 +24,55 @@

Home

{{if .profile.GivenNames}} - + {{end}} - {{if .profile.FamilyName}} + {{if .profile.FamilyName.Value}} - + {{end}} {{if .profile.FullName}} - + {{end}} {{if .profile.MobileNumber}} - + {{end}} {{if .profile.EmailAddress}} - + {{end}} - {{if .profile.DateOfBirth}} + {{if .dateOfBirth}} - + - {{end}} - {{if .profile.IsAgeVerified}} - - - - - {{end}} + {{end}} {{if .profile.Address}} - + {{end}} {{if .profile.Gender}} - + {{end}} {{if .profile.Nationality}} - + {{end}} {{if .profile.StructuredPostalAddress}} @@ -86,7 +80,7 @@

Home

ID:{{.profile.ID}}Remember Me ID:{{.rememberMeID}}
First Name:{{.profile.GivenNames}}{{.profile.GivenNames.Value}}
Family Name:{{.profile.FamilyName}}{{.profile.FamilyName.Value}}
Full Name:{{.profile.FullName}}{{.profile.FullName.Value}}
Mobile Number:{{.profile.MobileNumber}}{{.profile.MobileNumber.Value}}
Email Address:{{.profile.EmailAddress}}{{.profile.EmailAddress.Value}}
Date of Birth:{{.profile.DateOfBirth}}{{.dateOfBirth.Value}}
Is Age Verified:{{.profile.IsAgeVerified}}
Address:{{.profile.Address}}{{.profile.Address.Value}}
Gender:{{.profile.Gender}}{{.profile.Gender.Value}}
Nationality:{{.profile.Nationality}}{{.profile.Nationality.Value}}
Structured Postal Address: - {{range $key, $value := .profile.StructuredPostalAddress}} + {{range $key, $value := .profile.StructuredPostalAddress.Value}} diff --git a/yoti_test.go b/yoti_test.go index 0102aad8..2d079501 100644 --- a/yoti_test.go +++ b/yoti_test.go @@ -21,9 +21,9 @@ import ( const token = "NpdmVVGC-28356678-c236-4518-9de4-7a93009ccaf0-c5f92f2a-5539-453e-babc-9b06e1d6b7de" const encryptedToken = "b6H19bUCJhwh6WqQX_sEHWX9RP-A_ANr1fkApwA4Dp2nJQFAjrF9e6YCXhNBpAIhfHnN0iXubyXxXZMNwNMSQ5VOxkqiytrvPykfKQWHC6ypSbfy0ex8ihndaAXG5FUF-qcU8QaFPMy6iF3x0cxnY0Ij0kZj0Ng2t6oiNafb7AhT-VGXxbFbtZu1QF744PpWMuH0LVyBsAa5N5GJw2AyBrnOh67fWMFDKTJRziP5qCW2k4h5vJfiYr_EOiWKCB1d_zINmUm94ZffGXxcDAkq-KxhN1ZuNhGlJ2fKcFh7KxV0BqlUWPsIEiwS0r9CJ2o1VLbEs2U_hCEXaqseEV7L29EnNIinEPVbL4WR7vkF6zQCbK_cehlk2Qwda-VIATqupRO5grKZN78R9lBitvgilDaoE7JB_VFcPoljGQ48kX0wje1mviX4oJHhuO8GdFITS5LTbojGVQWT7LUNgAUe0W0j-FLHYYck3v84OhWTqads5_jmnnLkp9bdJSRuJF0e8pNdePnn2lgF-GIcyW_0kyGVqeXZrIoxnObLpF-YeUteRBKTkSGFcy7a_V_DLiJMPmH8UXDLOyv8TVt3ppzqpyUrLN2JVMbL5wZ4oriL2INEQKvw_boDJjZDGeRlu5m1y7vGDNBRDo64-uQM9fRUULPw-YkABNwC0DeShswzT00=" +const sdkID = "fake-sdk-id" func TestYotiClient_KeyLoad_Failure(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key-invalid-format.pem") var requester = func(uri string, headers map[string]string, httpRequestMethod string, contentBytes []byte) (result *httpResponse, err error) { @@ -43,7 +43,6 @@ func TestYotiClient_KeyLoad_Failure(t *testing.T) { } func TestYotiClient_HttpFailure_ReturnsFailure(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") var requester = func(uri string, headers map[string]string, httpRequestMethod string, contentBytes []byte) (result *httpResponse, err error) { @@ -62,7 +61,6 @@ func TestYotiClient_HttpFailure_ReturnsFailure(t *testing.T) { } func TestYotiClient_HttpFailure_ReturnsProfileNotFound(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") var requester = func(uri string, headers map[string]string, httpRequestMethod string, contentBytes []byte) (result *httpResponse, err error) { @@ -81,7 +79,6 @@ func TestYotiClient_HttpFailure_ReturnsProfileNotFound(t *testing.T) { } func TestYotiClient_SharingFailure_ReturnsFailure(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") var requester = func(uri string, headers map[string]string, httpRequestMethod string, contentBytes []byte) (result *httpResponse, err error) { @@ -101,7 +98,6 @@ func TestYotiClient_SharingFailure_ReturnsFailure(t *testing.T) { } func TestYotiClient_TokenDecodedSuccessfully(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") expectedAbsoluteURL := "/api/v1/profile/" + token @@ -132,7 +128,6 @@ func TestYotiClient_TokenDecodedSuccessfully(t *testing.T) { } func TestYotiClient_ParseProfile_Success(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") wrappedReceiptKey := "kyHPjq2+Y48cx+9yS/XzmW09jVUylSdhbP+3Q9Tc9p6bCEnyfa8vj38AIu744RzzE+Dc4qkSF21VfzQKtJVILfOXu5xRc7MYa5k3zWhjiesg/gsrv7J4wDyyBpHIJB8TWXnubYMbSYQJjlsfwyxE9kGe0YI08pRo2Tiht0bfR5Z/YrhAk4UBvjp84D+oyug/1mtGhKphA4vgPhQ9/y2wcInYxju7Q6yzOsXGaRUXR38Tn2YmY9OBgjxiTnhoYJFP1X9YJkHeWMW0vxF1RHxgIVrpf7oRzdY1nq28qzRg5+wC7cjRpS2i/CKUAo0oVG4pbpXsaFhaTewStVC7UFtA77JHb3EnF4HcSWMnK5FM7GGkL9MMXQenh11NZHKPWXpux0nLZ6/vwffXZfsiyTIcFL/NajGN8C/hnNBljoQ+B3fzWbjcq5ueUOPwARZ1y38W83UwMynzkud/iEdHLaZIu4qUCRkfSxJg7Dc+O9/BdiffkOn2GyFmNjVeq754DCUypxzMkjYxokedN84nK13OU4afVyC7t5DDxAK/MqAc69NCBRLqMi5f8BMeOZfMcSWPGC9a2Qu8VgG125TuZT4+wIykUhGyj3Bb2/fdPsxwuKFR+E0uqs0ZKvcv1tkNRRtKYBqTacgGK9Yoehg12cyLrITLdjU1fmIDn4/vrhztN5w=" @@ -183,12 +178,12 @@ func TestYotiClient_ParseProfile_Success(t *testing.T) { expectedSelfieValue := "selfie0123456789" if profile.Selfie() == nil { t.Error(`expected selfie attribute, but it was not present in the returned profile`) - } else if !cmp.Equal(profile.Selfie().Value.Data, []byte(expectedSelfieValue)) { - t.Errorf("expected selfie %q, instead received %q", expectedSelfieValue, string(profile.Selfie().Value.Data)) + } else if !cmp.Equal(profile.Selfie().Value().Data, []byte(expectedSelfieValue)) { + t.Errorf("expected selfie %q, instead received %q", expectedSelfieValue, string(profile.Selfie().Value().Data)) } - if !cmp.Equal(profile.MobileNumber().Value, "phone_number0123456789") { - t.Errorf("expected mobileNumber %q, instead received %q", "phone_number0123456789", profile.MobileNumber().Value) + if !cmp.Equal(profile.MobileNumber().Value(), "phone_number0123456789") { + t.Errorf("expected mobileNumber %q, instead received %q", "phone_number0123456789", profile.MobileNumber().Value()) } expectedDoB := time.Date(1980, time.January, 1, 0, 0, 0, 0, time.UTC) @@ -200,13 +195,12 @@ func TestYotiClient_ParseProfile_Success(t *testing.T) { if actualDoB == nil { t.Error(`expected date of birth, but it was not present in the returned profile`) - } else if !actualDoB.Value.Equal(expectedDoB) { - t.Errorf("expected date of birth: %q, instead received: %q", expectedDoB.Format(time.UnixDate), actualDoB.Value.Format(time.UnixDate)) + } else if !actualDoB.Value().Equal(expectedDoB) { + t.Errorf("expected date of birth: %q, instead received: %q", expectedDoB.Format(time.UnixDate), actualDoB.Value().Format(time.UnixDate)) } } func TestYotiClient_ParseWithoutProfile_Success(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") wrappedReceiptKey := "kyHPjq2+Y48cx+9yS/XzmW09jVUylSdhbP+3Q9Tc9p6bCEnyfa8vj38AIu744RzzE+Dc4qkSF21VfzQKtJVILfOXu5xRc7MYa5k3zWhjiesg/gsrv7J4wDyyBpHIJB8TWXnubYMbSYQJjlsfwyxE9kGe0YI08pRo2Tiht0bfR5Z/YrhAk4UBvjp84D+oyug/1mtGhKphA4vgPhQ9/y2wcInYxju7Q6yzOsXGaRUXR38Tn2YmY9OBgjxiTnhoYJFP1X9YJkHeWMW0vxF1RHxgIVrpf7oRzdY1nq28qzRg5+wC7cjRpS2i/CKUAo0oVG4pbpXsaFhaTewStVC7UFtA77JHb3EnF4HcSWMnK5FM7GGkL9MMXQenh11NZHKPWXpux0nLZ6/vwffXZfsiyTIcFL/NajGN8C/hnNBljoQ+B3fzWbjcq5ueUOPwARZ1y38W83UwMynzkud/iEdHLaZIu4qUCRkfSxJg7Dc+O9/BdiffkOn2GyFmNjVeq754DCUypxzMkjYxokedN84nK13OU4afVyC7t5DDxAK/MqAc69NCBRLqMi5f8BMeOZfMcSWPGC9a2Qu8VgG125TuZT4+wIykUhGyj3Bb2/fdPsxwuKFR+E0uqs0ZKvcv1tkNRRtKYBqTacgGK9Yoehg12cyLrITLdjU1fmIDn4/vrhztN5w=" @@ -270,7 +264,6 @@ func TestYotiClient_SupportedHttpMethod(t *testing.T) { } func TestYotiClient_PerformAmlCheck_Success(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") var requester = func(uri string, headers map[string]string, httpRequestMethod string, contentBytes []byte) (result *httpResponse, err error) { @@ -304,7 +297,6 @@ func TestYotiClient_PerformAmlCheck_Success(t *testing.T) { } func TestYotiClient_PerformAmlCheck_Unsuccessful(t *testing.T) { - sdkID := "fake-sdk-id" key, _ := ioutil.ReadFile("test-key.pem") var requester = func(uri string, headers map[string]string, httpRequestMethod string, contentBytes []byte) (result *httpResponse, err error) { @@ -560,8 +552,8 @@ func TestProfile_GetAttribute_String(t *testing.T) { t.Errorf("Retrieved attribute does not have the correct name. Expected %q, actual: %q", attributeName, att.Name) } - if !cmp.Equal(att.Value.(string), attributeValueString) { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value) + if !cmp.Equal(att.Value().(string), attributeValueString) { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value()) } } @@ -583,8 +575,8 @@ func TestProfile_GetAttribute_Time(t *testing.T) { result := createProfileWithSingleAttribute(attr) att := result.GetAttribute(attributeName) - if !cmp.Equal(expectedDate, att.Value.(*time.Time).UTC()) { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", expectedDate, att.Value.(*time.Time)) + if !cmp.Equal(expectedDate, att.Value().(*time.Time).UTC()) { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", expectedDate, att.Value().(*time.Time)) } } @@ -602,8 +594,8 @@ func TestProfile_GetAttribute_Jpeg(t *testing.T) { result := createProfileWithSingleAttribute(attr) att := result.GetAttribute(attributeName) - if !cmp.Equal(att.Value.([]byte), attributeValue) { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value) + if !cmp.Equal(att.Value().([]byte), attributeValue) { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value()) } } @@ -621,8 +613,8 @@ func TestProfile_GetAttribute_Png(t *testing.T) { result := createProfileWithSingleAttribute(attr) att := result.GetAttribute(attributeName) - if !cmp.Equal(att.Value.([]byte), attributeValue) { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value) + if !cmp.Equal(att.Value().([]byte), attributeValue) { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value()) } } @@ -641,7 +633,7 @@ func TestProfile_GetAttribute_Bool(t *testing.T) { result := createProfileWithSingleAttribute(attr) att := result.GetAttribute(attributeName) - boolValue, err := strconv.ParseBool(att.Value.(string)) + boolValue, err := strconv.ParseBool(att.Value().(string)) if err != nil { t.Errorf("Unable to parse string to bool. Error: %s", err) } @@ -672,7 +664,7 @@ func TestProfile_GetAttribute_JSON(t *testing.T) { result := createProfileWithSingleAttribute(attr) att := result.GetAttribute(attributeName) - retrievedAttributeInterfaceArray := att.Value.([]interface{}) + retrievedAttributeInterfaceArray := att.Value().([]interface{}) parsedMap := retrievedAttributeInterfaceArray[0].(map[string]interface{}) actualAddressFormat := parsedMap["address_format"] @@ -700,13 +692,13 @@ func TestProfile_GetAttribute_Undefined(t *testing.T) { t.Errorf("Retrieved attribute does not have the correct name. Expected %q, actual: %q", attributeName, att.Name) } - if !cmp.Equal(att.Value.(string), attributeValueString) { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value) + if !cmp.Equal(att.Value().(string), attributeValueString) { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, att.Value()) } } func TestProfile_GetAttribute_ReturnsNil(t *testing.T) { result := Profile{ - AttributeSlice: []*yotiprotoattr_v3.Attribute{}, + attributeSlice: []*yotiprotoattr_v3.Attribute{}, } attribute := result.GetAttribute("attributeName") @@ -730,8 +722,8 @@ func TestProfile_StringAttribute(t *testing.T) { result := createProfileWithSingleAttribute(as) - if result.Nationality().Value != attributeValueString { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValueString, result.Nationality().Value) + if result.Nationality().Value() != attributeValueString { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValueString, result.Nationality().Value()) } if !cmp.Equal(result.Nationality().ContentType, yotiprotoattr_v3.ContentType_STRING) { @@ -757,8 +749,8 @@ func TestProfile_AttributeProperty_RetrievesAttribute(t *testing.T) { t.Errorf("Retrieved attribute does not have the correct name. Expected %q, actual: %q", attributeName, selfie.Name) } - if !reflect.DeepEqual(attributeValue, selfie.Value.Data) { - t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, selfie.Value.Data) + if !reflect.DeepEqual(attributeValue, selfie.Value().Data) { + t.Errorf("Retrieved attribute does not have the correct value. Expected %q, actual: %q", attributeValue, selfie.Value().Data) } if !cmp.Equal(selfie.ContentType, yotiprotoattr_v3.ContentType_PNG) { @@ -780,8 +772,8 @@ func TestAttributeImage_Image_Png(t *testing.T) { result := createProfileWithSingleAttribute(attributeImage) selfie := result.Selfie() - if !cmp.Equal(selfie.Value.Data, byteValue) { - t.Errorf("Retrieved attribute does not have the correct Image. Expected %v, actual: %v", byteValue, selfie.Value.Data) + if !cmp.Equal(selfie.Value().Data, byteValue) { + t.Errorf("Retrieved attribute does not have the correct Image. Expected %v, actual: %v", byteValue, selfie.Value().Data) } } @@ -799,8 +791,8 @@ func TestAttributeImage_Image_Jpeg(t *testing.T) { result := createProfileWithSingleAttribute(attributeImage) selfie := result.Selfie() - if !cmp.Equal(selfie.Value.Data, byteValue) { - t.Errorf("Retrieved attribute does not have the correct byte value. Expected %v, actual: %v", byteValue, selfie.Value.Data) + if !cmp.Equal(selfie.Value().Data, byteValue) { + t.Errorf("Retrieved attribute does not have the correct byte value. Expected %v, actual: %v", byteValue, selfie.Value().Data) } } @@ -817,8 +809,8 @@ func TestAttributeImage_Image_Default(t *testing.T) { result := createProfileWithSingleAttribute(attributeImage) selfie := result.Selfie() - if !cmp.Equal(selfie.Value.Data, byteValue) { - t.Errorf("Retrieved attribute does not have the correct byte value. Expected %v, actual: %v", byteValue, selfie.Value.Data) + if !cmp.Equal(selfie.Value().Data, byteValue) { + t.Errorf("Retrieved attribute does not have the correct byte value. Expected %v, actual: %v", byteValue, selfie.Value().Data) } } func TestAttributeImage_Base64Selfie_Png(t *testing.T) { @@ -838,7 +830,7 @@ func TestAttributeImage_Base64Selfie_Png(t *testing.T) { expectedBase64Selfie := "data:image/png;base64;," + base64ImageExpectedValue - base64Selfie, err := result.Selfie().Value.Base64URL() + base64Selfie, err := result.Selfie().Value().Base64URL() if err != nil { t.Error(err) @@ -866,7 +858,7 @@ func TestAttributeImage_Base64URL_Jpeg(t *testing.T) { expectedBase64Selfie := "data:image/jpeg;base64;," + base64ImageExpectedValue - base64Selfie, err := result.Selfie().Value.Base64URL() + base64Selfie, err := result.Selfie().Value().Base64URL() if err != nil { t.Error(err) @@ -906,7 +898,11 @@ func TestAnchorParser_Passport(t *testing.T) { t.Error(err) } - actualAnchor := actualStructuredPostalAddress.Anchors[0] + actualAnchor := actualStructuredPostalAddress.Anchors()[0] + + if actualAnchor != actualStructuredPostalAddress.Sources()[0] { + t.Error("Anchors and Sources should be the same when there is only one Source") + } if actualAnchor.Type != anchor.AnchorTypeSource { t.Errorf("Parsed anchor type is incorrect. Expected: %q, actual: %q", anchor.AnchorTypeSource, actualAnchor.Type) @@ -944,7 +940,12 @@ func TestAnchorParser_DrivingLicense(t *testing.T) { result := createProfileWithSingleAttribute(attribute) - resultAnchor := result.Gender().Anchors[0] + genderAttribute := result.Gender() + resultAnchor := genderAttribute.Anchors()[0] + + if resultAnchor != genderAttribute.Sources()[0] { + t.Error("Anchors and Sources should be the same when there is only one Source") + } if resultAnchor.Type != anchor.AnchorTypeSource { t.Errorf("Parsed anchor type is incorrect. Expected: %q, actual: %q", anchor.AnchorTypeSource, resultAnchor.Type) @@ -987,7 +988,11 @@ func TestAnchorParser_YotiAdmin(t *testing.T) { t.Error(err) } - resultAnchor := DoB.Anchors[0] + resultAnchor := DoB.Anchors()[0] + + if resultAnchor != DoB.Verifiers()[0] { + t.Error("Anchors and Verifiers should be the same when there is only one Verifier") + } if resultAnchor.Type != anchor.AnchorTypeVerifier { t.Errorf("Parsed anchor type is incorrect. Expected: %q, actual: %q", anchor.AnchorTypeVerifier, resultAnchor.Type) @@ -1013,12 +1018,44 @@ func TestAnchorParser_YotiAdmin(t *testing.T) { AssertServerCertSerialNo(t, "256616937783084706710155170893983549581", actualSerialNo) } +func TestAnchors_Unknown(t *testing.T) { + unknownAnchor := &anchor.Anchor{ + Type: anchor.AnchorTypeUnknown, + } + + anchorSlice := append([]*anchor.Anchor{}, unknownAnchor) + + sources := anchor.GetSources(anchorSlice) + if len(sources) > 0 { + t.Error("Unknown anchor should not be returned by GetSources") + } + + verifiers := anchor.GetVerifiers(anchorSlice) + if len(verifiers) > 0 { + t.Error("Unknown anchor should not be returned by GetVerifiers") + } +} + +func TestAnchors_None(t *testing.T) { + anchorSlice := []*anchor.Anchor{} + + sources := anchor.GetSources(anchorSlice) + if len(sources) > 0 { + t.Error("GetSources should not return anything with empty anchors") + } + + verifiers := anchor.GetVerifiers(anchorSlice) + if len(verifiers) > 0 { + t.Error("GetVerifiers should not return anything with empty anchors") + } +} + func createProfileWithSingleAttribute(attr *yotiprotoattr_v3.Attribute) Profile { var attributeSlice []*yotiprotoattr_v3.Attribute attributeSlice = append(attributeSlice, attr) return Profile{ - AttributeSlice: attributeSlice, + attributeSlice: attributeSlice, } } diff --git a/yoticlient.go b/yoticlient.go index 951abe06..a4ce2af4 100644 --- a/yoticlient.go +++ b/yoticlient.go @@ -51,7 +51,7 @@ type ActivityDetails struct { RememberMeID string } -// Deprecated: Will be removed in v3.0.0. Use `GetProfile` instead. GetUserProfile requests information about a Yoti user using the one time use token generated by the Yoti login process. +// Deprecated: Will be removed in v3.0.0. Use `GetActivityDetails` instead. GetUserProfile requests information about a Yoti user using the one time use token generated by the Yoti login process. // It returns the outcome of the request. If the request was successful it will include the users details, otherwise // it will specify a reason the request failed. func (client *Client) GetUserProfile(token string) (userProfile UserProfile, firstError error) { @@ -135,7 +135,7 @@ func getActivityDetails(requester httpRequester, encryptedToken, sdkID string, k attributeSlice := createAttributeSlice(id, attributeList) profile := Profile{ - AttributeSlice: attributeSlice, + attributeSlice: attributeSlice, } var formattedAddress string @@ -157,7 +157,7 @@ func getActivityDetails(requester httpRequester, encryptedToken, sdkID string, k Anchors: protoStructuredPostalAddress.Anchors, } - profile.AttributeSlice = append(profile.AttributeSlice, addressAttribute) + profile.attributeSlice = append(profile.attributeSlice, addressAttribute) } activityDetails = ActivityDetails{ @@ -182,7 +182,7 @@ func getActivityDetails(requester httpRequester, encryptedToken, sdkID string, k } func getProtobufAttribute(profile Profile, key string) *yotiprotoattr_v3.Attribute { - for _, v := range profile.AttributeSlice { + for _, v := range profile.attributeSlice { if v.Name == attrConstStructuredPostalAddress { return v } @@ -318,7 +318,7 @@ func ensureAddressProfile(profile Profile) (address string, err error) { if structuredPostalAddress, err = profile.StructuredPostalAddress(); err == nil { if (structuredPostalAddress != nil && !reflect.DeepEqual(structuredPostalAddress, attribute.JSONAttribute{})) { var formattedAddress string - formattedAddress, err = retrieveFormattedAddressFromStructuredPostalAddress(structuredPostalAddress.Value) + formattedAddress, err = retrieveFormattedAddressFromStructuredPostalAddress(structuredPostalAddress.Value()) if err == nil { return formattedAddress, nil } diff --git a/yotiprofile.go b/yotiprofile.go index dec11841..d129e23f 100644 --- a/yotiprofile.go +++ b/yotiprofile.go @@ -19,18 +19,22 @@ const ( attrConstNationality = "nationality" ) -//Profile represents the details retrieved for a particular +// Profile represents the details retrieved for a particular user. Consists of +// Yoti attributes: a small piece of information about a Yoti user such as a +// photo of the user or the user's date of birth. type Profile struct { - // AttributeSlice represents a map of the Yoti attributes, each attribute is a small piece of information about a Yoti user such as a photo of the user or the - // user's date of birth. - AttributeSlice []*yotiprotoattr_v3.Attribute + attributeSlice []*yotiprotoattr_v3.Attribute } // Selfie is a photograph of the user. Will be nil if not provided by Yoti func (p Profile) Selfie() *attribute.ImageAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstSelfie { - return attribute.NewImage(a) + attribute, err := attribute.NewImage(a) + + if err == nil { + return attribute + } } } return nil @@ -38,7 +42,7 @@ func (p Profile) Selfie() *attribute.ImageAttribute { // GivenNames represents the user's given names. Will be nil if not provided by Yoti func (p Profile) GivenNames() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstGivenNames { return attribute.NewString(a) } @@ -48,7 +52,7 @@ func (p Profile) GivenNames() *attribute.StringAttribute { // FamilyName represents the user's family name. Will be nil if not provided by Yoti func (p Profile) FamilyName() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstFamilyName { return attribute.NewString(a) } @@ -58,7 +62,7 @@ func (p Profile) FamilyName() *attribute.StringAttribute { //FullName represents the user's full name. Will be nil if not provided by Yoti func (p Profile) FullName() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstFullName { return attribute.NewString(a) } @@ -68,7 +72,7 @@ func (p Profile) FullName() *attribute.StringAttribute { // MobileNumber represents the user's mobile phone number. Will be nil if not provided by Yoti func (p Profile) MobileNumber() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstMobileNumber { return attribute.NewString(a) } @@ -78,7 +82,7 @@ func (p Profile) MobileNumber() *attribute.StringAttribute { // EmailAddress represents the user's email address. Will be nil if not provided by Yoti func (p Profile) EmailAddress() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstEmailAddress { return attribute.NewString(a) } @@ -88,7 +92,7 @@ func (p Profile) EmailAddress() *attribute.StringAttribute { // DateOfBirth represents the user's date of birth. Will be nil if not provided by Yoti. Has an err value which will be filled if there is an error parsing the date. func (p Profile) DateOfBirth() (*attribute.TimeAttribute, error) { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstDateOfBirth { return attribute.NewTime(a) } @@ -98,7 +102,7 @@ func (p Profile) DateOfBirth() (*attribute.TimeAttribute, error) { // Address represents the user's address. Will be nil if not provided by Yoti func (p Profile) Address() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstAddress { return attribute.NewString(a) } @@ -108,7 +112,7 @@ func (p Profile) Address() *attribute.StringAttribute { // StructuredPostalAddress represents the user's address in a JSON format. Will be nil if not provided by Yoti func (p Profile) StructuredPostalAddress() (*attribute.JSONAttribute, error) { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstStructuredPostalAddress { return attribute.NewJSON(a) } @@ -118,7 +122,7 @@ func (p Profile) StructuredPostalAddress() (*attribute.JSONAttribute, error) { // Gender represents the user's gender. Will be nil if not provided by Yoti func (p Profile) Gender() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstGender { return attribute.NewString(a) } @@ -128,7 +132,7 @@ func (p Profile) Gender() *attribute.StringAttribute { // Nationality represents the user's nationality. Will be nil if not provided by Yoti func (p Profile) Nationality() *attribute.StringAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attrConstNationality { return attribute.NewString(a) } @@ -138,7 +142,7 @@ func (p Profile) Nationality() *attribute.StringAttribute { // GetAttribute retrieve an attribute by name on the Yoti profile. Will return nil if attribute is not present. func (p Profile) GetAttribute(attributeName string) *attribute.GenericAttribute { - for _, a := range p.AttributeSlice { + for _, a := range p.attributeSlice { if a.Name == attributeName { return attribute.NewGeneric(a) }
{{$key}} {{$value}}