lib/ga4gh/passport.go (100 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ga4gh import ( "context" "fmt" "github.com/go-jose/go-jose/v3/jwt" /* copybara-comment */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/kms" /* copybara-comment: kms */ glog "github.com/golang/glog" /* copybara-comment */ ) const ( jwtHeaderJKU = "jku" ) // Passport represents a GA4GH Passport. // http://bit.ly/ga4gh-passport-v1#overview type Passport struct { // Access is the Access Token for the Passport. Access *Access // Visas contains the list of Visas for the Passport. Visas []*Visa `json:"ga4gh_passport_v1,omitempty"` } // Access represents a GA4GH Access Token. // http://bit.ly/ga4gh-passport-v1#overview type Access struct { // jwt for the Access. jwt AccessJWT // data is unmarhsalled data contained in access jwt. data *AccessData } // AccessJWT is a string containing a JWT Access object. type AccessJWT string // AccessData is used to create a new Access. type AccessData struct { // StdClaims is embeded for standard JWT claims. StdClaims // Scope ... Scope string `json:"scope,omitempty"` // TODO: Replace identities with LinkedIdentities visas. Identities map[string][]string `json:"identities,omitempty"` Patient string `json:"patient,omitempty"` // FhirUser is the SMART-on-FHIR principal identity. FhirUser string `json:"fhirUser,omitempty"` } // NewAccessFromJWT creates a new Access from a given JWT. // Returns error if the JWT is not the JWT of a Access. // Does not verify the signature on the JWT. func NewAccessFromJWT(j AccessJWT) (*Access, error) { glog.V(1).Info("NewAccessFromJWT()") d, err := accessDataFromJWT(j) if err != nil { return nil, err } return &Access{ jwt: j, data: d, }, nil } // NewAccessFromData creates a new Access. // // keyID identifies the key used by issuer to sign the JWT. // Visit the issuer's JWKS endpoint to obtain the keys and find the public key corresponding to the keyID. // To find the issuer's JWKS endpoint, visit "[issuer]/.well-known/openid-configuration" // "jku" in JWT header is not allowed for Access. func NewAccessFromData(ctx context.Context, d *AccessData, signer kms.Signer) (*Access, error) { glog.V(1).Info("NewAccessFromData()") j, err := accessJWTFromData(ctx, d, signer) if err != nil { return nil, err } return &Access{ jwt: j, data: d, }, nil } // JWT returns the JWT of a Access. func (p *Access) JWT() AccessJWT { return p.jwt } // Data returns the data of a Access. func (p *Access) Data() *AccessData { return p.data } // accessDataVisaJWT is internally used for marshaling and unmarshalling. type accessDataVisaJWT struct { // StdClaims is embeded for standard JWT claims. StdClaims // Scope ... Scope string `json:"scope,omitempty"` // TODO: Replace identities with LinkedIdentities visas. Identities map[string][]string `json:"identities,omitempty"` } func accessJWTFromData(ctx context.Context, d *AccessData, signer kms.Signer) (AccessJWT, error) { signed, err := signer.SignJWT(ctx, d, nil) if err != nil { err = fmt.Errorf("SignJWT() failed: %v", err) glog.V(1).Info(err) return "", err } return AccessJWT(signed), nil } func toAccessDataWithVisaJWT(d *AccessData) *accessDataVisaJWT { m := &accessDataVisaJWT{ StdClaims: d.StdClaims, Scope: d.Scope, Identities: d.Identities, } return m } func accessDataFromJWT(j AccessJWT) (*AccessData, error) { m := &accessDataVisaJWT{} tok, err := jwt.ParseSigned(string(j)) if err != nil { return nil, fmt.Errorf("ParseSigned() failed: %v", err) } if err := tok.UnsafeClaimsWithoutVerification(m); err != nil { return nil, fmt.Errorf("UnsafeClaimsWithoutVerification() failed: %v", err) } d, err := toAccessData(m) if err != nil { return nil, err } return d, nil } func toAccessData(m *accessDataVisaJWT) (*AccessData, error) { d := &AccessData{ StdClaims: m.StdClaims, Scope: m.Scope, Identities: m.Identities, } return d, nil }