Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDK-443] Add Sources & Verifiers #23

Merged
merged 8 commits into from
Nov 16, 2018
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
Expand Down
58 changes: 47 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,23 @@ 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
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()
if err != nil {
//handle error
Expand All @@ -130,11 +132,45 @@ if err != nil {
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
givenNamesFirstAnchor := userProfile.GivenNames().Anchors()[0]

anchorType := givenNamesFirstAnchor.Type
signedTimestamp := givenNamesFirstAnchor.SignedTimestamp().Timestamp
echarrod marked this conversation as resolved.
Show resolved Hide resolved
subType := givenNamesFirstAnchor.SubType()
value := givenNamesFirstAnchor.Value()
```

## Handling Users

When you retrieve the user profile, you receive a user ID generated by Yoti exclusively for your application.
Expand Down Expand Up @@ -200,7 +236,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"

Expand Down
23 changes: 21 additions & 2 deletions anchor/anchors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we already have Type as a string on each Anchor struct (as taken from the certificate), can't we just filter on that? What's the additional type enum for?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update, I'm a bit wrong here, we've just talked this through....

The anchor type comes to us as an 'extensionOId' e.g. '1.3.6.1.4.1.47127.1.1.1'. We are not given a nice, neat String. The String's are introduced by the SDK to make the OId more meaningful.

The apprach here matches the Java SDK - map the OId to an Enum as a convenient way to convert that OId into a nice string. If we ever get an OId we don't know about, we map it to unknown.

We might to want to improve on this so I've created SDK-683, but for now the work done here is good to go.

}

func filterAnchors(anchors []*Anchor, anchorType Type) (result []*Anchor) {
for _, v := range anchors {
if v.Type == anchorType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the Anchor Object include the AnchorType?
If so what's the value of the type e.g 'source' or the old e.g '1.3.6.1.4.1.47127.1.1.1'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in https://lampkicking.atlassian.net/browse/SDK-683: having enum type is fine for the foreseeable future, and we won't expose OID

result = append(result, v)
}
}
return result
}
36 changes: 32 additions & 4 deletions attribute/genericattribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this hold a list of Anchors e.g [AnchorObj1, AnchorObj2, ...] or a Map of Anchors as ['1.3.6.1.4.1.47127.1.1.1' => AnchorObj, ...]?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it holds a list as this is a slice. I don't think Go has associative arrays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is a slice, a bit like a list - there's basically nothing a list can do that a slice can't (in Go), but lists have the disadvantage of not being strongly typed. You can have a map, but I don't think that is necessary here

sources []*anchor.Anchor
verifiers []*anchor.Anchor
}

//NewGeneric creates a new generic attribute
Expand Down Expand Up @@ -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
}
27 changes: 6 additions & 21 deletions attribute/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 38 additions & 8 deletions attribute/imageattribute.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package attribute

import (
"errors"

"github.com/getyoti/yoti-go-sdk/anchor"
"github.com/getyoti/yoti-go-sdk/yotiprotoattr_v3"
)

//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:
Expand All @@ -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
}
36 changes: 32 additions & 4 deletions attribute/jsonattribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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
}
Loading