lib/scim/scim_helper.go (100 lines of code) (raw):
// Copyright 2020 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 scim
import (
"context"
"strings"
"time"
"google.golang.org/grpc/codes" /* copybara-comment */
"google.golang.org/grpc/status" /* copybara-comment */
"github.com/pborman/uuid" /* copybara-comment */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/ga4gh" /* copybara-comment: ga4gh */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/kms" /* copybara-comment: kms */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */
pb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/common/v1" /* copybara-comment: go_proto */
)
// UpdateIdentityInAccount updates the identity in a existing account.
func UpdateIdentityInAccount(ctx context.Context, id *ga4gh.Identity, provider string, acct *pb.Account, encryption kms.Encryption) (*pb.Account, error) {
index, ca := findConnectedAccount(id, provider, acct)
if ca == nil {
// internal error because acct found by account lookup by id.
return nil, status.Error(codes.Internal, "can not find ConnectedAccount in account")
}
newCa, err := toConnectedAccount(ctx, encryption, id, provider)
if err != nil {
return nil, err
}
newCa.Revision = ca.Revision + 1
acct.ConnectedAccounts[index] = newCa
// TODO: update account properties and account profile
return acct, nil
}
// NewAccount for given identity.
func NewAccount(ctx context.Context, encryption kms.Encryption, id *ga4gh.Identity, provider, accountNamePrefix string, genAccountNameLen int) (*pb.Account, *pb.AccountLookup, error) {
n := accountNamePrefix + strings.Replace(uuid.New(), "-", "", -1)
n = n[:genAccountNameLen]
now := time.Now()
ca, err := toConnectedAccount(ctx, encryption, id, provider)
if err != nil {
return nil, nil, err
}
acct := &pb.Account{
Revision: 0,
Profile: setupAccountProfile(id),
Properties: setupAccountProperties(id, n, now, now),
ConnectedAccounts: []*pb.ConnectedAccount{ca},
State: storage.StateActive,
Ui: map[string]string{},
}
lookup := &pb.AccountLookup{
Subject: acct.Properties.Subject,
Revision: 0,
State: storage.StateActive,
}
return acct, lookup, nil
}
func findConnectedAccount(id *ga4gh.Identity, provider string, acct *pb.Account) (int, *pb.ConnectedAccount) {
for i, ca := range acct.ConnectedAccounts {
if ca.Provider == provider && ca.Properties.Subject == id.Subject {
return i, ca
}
}
return -1, nil
}
// toConnectedAccount converts identity ConnectedAccount.
func toConnectedAccount(ctx context.Context, encryption kms.Encryption, id *ga4gh.Identity, provider string) (*pb.ConnectedAccount, error) {
now := time.Now()
var visas [][]byte
for _, tok := range id.VisaJWTs {
encrypted, err := encryption.Encrypt(ctx, []byte(tok), "")
if err != nil {
return nil, status.Errorf(codes.Unavailable, "encrypt visa failed: %v", err)
}
visas = append(visas, encrypted)
}
return &pb.ConnectedAccount{
Passport: &pb.Passport{InternalEncryptedVisas: visas},
Profile: setupAccountProfile(id),
Properties: setupAccountProperties(id, id.Subject, now, now),
Provider: provider,
Refreshed: float64(now.UnixNano()) / 1e9,
Revision: 1,
LinkRevision: 1,
}, nil
}
func setupAccountProfile(id *ga4gh.Identity) *pb.AccountProfile {
return &pb.AccountProfile{
Username: id.Username,
Name: id.Name,
GivenName: id.GivenName,
FamilyName: id.FamilyName,
MiddleName: id.MiddleName,
Profile: id.Profile,
Picture: id.Picture,
ZoneInfo: id.ZoneInfo,
Locale: id.Locale,
Language: id.Locale, // OIDC Identity does not have "language"
}
}
func setupAccountProperties(id *ga4gh.Identity, subject string, created, modified time.Time) *pb.AccountProperties {
return &pb.AccountProperties{
Subject: subject,
Email: id.Email,
EmailVerified: id.EmailVerified,
Created: float64(created.UnixNano()) / 1e9,
Modified: float64(modified.UnixNano()) / 1e9,
}
}