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
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
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,24 @@ userProfile.GetAttribute("age_over:18").Value.(string)

GetAttribute returns an interface, the value can be acquired through a type assertion.


You can retrieve the anchors, sources and verifiers for each attribute 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
originServerCerts := givenNamesFirstAnchor.OriginServerCerts()
echarrod marked this conversation as resolved.
Show resolved Hide resolved
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 +218,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
}
14 changes: 10 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
echarrod marked this conversation as resolved.
Show resolved Hide resolved
Sources []*anchor.Anchor
Verifiers []*anchor.Anchor
}

//NewGeneric creates a new generic attribute
Expand Down Expand Up @@ -49,12 +51,16 @@ 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,
echarrod marked this conversation as resolved.
Show resolved Hide resolved
Anchors: parsedAnchors,
Sources: anchor.GetSources(parsedAnchors),
Verifiers: anchor.GetVerifiers(parsedAnchors),
}
}
15 changes: 9 additions & 6 deletions attribute/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package attribute

import (
"encoding/base64"
"log"
"fmt"
)

//ImageType Image format
Expand Down Expand Up @@ -30,19 +30,22 @@ var mimeTypeMap = map[ImageType]string{

// 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) {
func GetMIMEType(imageType ImageType) (string, error) {
if val, ok := mimeTypeMap[imageType]; ok {
return val
return val, nil
}

log.Printf("Unable to find a matching MIME type for value type %q", imageType)
return
return "", fmt.Errorf("Unable to find a matching MIME type for value type %q", imageType)
}

// Base64URL is the Image encoded as a base64 URL
func (image *Image) Base64URL() (string, error) {
base64EncodedImage := base64.StdEncoding.EncodeToString(image.Data)
contentType := GetMIMEType(image.Type)
contentType, err := GetMIMEType(image.Type)
echarrod marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return "", err
}

return "data:" + contentType + ";base64;," + base64EncodedImage, nil
}
12 changes: 9 additions & 3 deletions attribute/imageattribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ 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
Expand All @@ -27,6 +29,8 @@ func NewImage(a *yotiprotoattr_v3.Attribute) *ImageAttribute {
imageType = ImageTypeOther
}

parsedAnchors := anchor.ParseAnchors(a.Anchors)

return &ImageAttribute{
Attribute: &yotiprotoattr_v3.Attribute{
Name: a.Name,
Expand All @@ -36,6 +40,8 @@ func NewImage(a *yotiprotoattr_v3.Attribute) *ImageAttribute {
Data: a.Value,
Type: imageType,
},
Anchors: anchor.ParseAnchors(a.Anchors),
Anchors: parsedAnchors,
Sources: anchor.GetSources(parsedAnchors),
Verifiers: anchor.GetVerifiers(parsedAnchors),
}
}
10 changes: 8 additions & 2 deletions attribute/jsonattribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ 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
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 Down
14 changes: 10 additions & 4 deletions attribute/stringattribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,24 @@ 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),
}
}
14 changes: 10 additions & 4 deletions attribute/timeattribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,12 +26,16 @@ 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
}
7 changes: 4 additions & 3 deletions examples/aml/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
2 changes: 1 addition & 1 deletion examples/profile/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>Yoti Example Project</title>
<script src="https://sdk.yoti.com/clients/browser.2.1.0.js"></script>
<script src="https://sdk.yoti.com/clients/browser.2.2.0.js"></script>
</head>
<body style="text-align: center; margin: 200px 0">
<a class="btn button yoti-connect learn-more btn-primary" data-target='embed' data-yoti-application-id='{{.yotiApplicationID}}'>Use Yoti</a>
Expand Down
61 changes: 40 additions & 21 deletions examples/profile/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"html/template"
"image"
"image/jpeg"
"io"
"io/ioutil"
"log"
"net/http"
Expand Down Expand Up @@ -36,40 +37,58 @@ 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

t, err := template.ParseFiles("profile.html")
if err != nil {
fmt.Println(err)
return
}
var base64URL string
base64URL, err = userProfile.Selfie().Value.Base64URL()

if err != nil {
log.Fatalf("Unable to retrieve `YOTI_KEY_FILE_PATH`. Error: %q", err)
}

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,
}

decodedImage := decodeImage(userProfile.Selfie().Value.Data)
file := createImage()
saveImage(decodedImage, file)

t.Execute(w, templateVars)
} else {
var t *template.Template
t, err = template.ParseFiles("profile.html")
if err != nil {
fmt.Println(err)
return
}

t.Execute(w, templateVars)
}

func main() {
Expand Down Expand Up @@ -131,7 +150,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

Expand Down
Loading