lib/ga4gh/condition.go (93 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"
glog "github.com/golang/glog" /* copybara-comment */
cpb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/common/v1" /* copybara-comment: go_proto */
)
// Conditions represent a GA4GH Passport Visa condition field sub-object.
// https://docs.google.com/document/d/1NySsYM1V9ssxk_k4RH37fU4tJK8x37YMmDxnn_45FvQ/
type Conditions [][]Condition
// CheckConditions checks if the given list of Assertions satisfies the
// given Conditions.
// Conditions is a DNF, an OR of Clauses, each Clause an AND of Literals,
// each Literal a Condition.
// A list of Assertions satisfies a Condition if at least one of Assertions
// makes the Condition true.
// Visas which cannot be verified will be ignored.
func CheckConditions(ctx context.Context, c Conditions, vs []*Visa, f JWTVerifier) error {
glog.V(1).Info("CheckConditions")
if len(c) == 0 {
return nil
}
for _, clause := range c {
if err := checkClause(ctx, clause, vs, f); err == nil {
return nil
}
}
return fmt.Errorf("insufficient visas")
}
// Clause is an AND of Literals.
type Clause []Condition
// Literal is a Condition.
type Literal = Condition
func checkClause(ctx context.Context, c Clause, vs []*Visa, f JWTVerifier) error {
glog.V(1).Info("checkClause")
for _, literal := range c {
if err := checkLiteral(ctx, literal, vs, f); err != nil {
return err
}
}
return nil
}
func checkLiteral(ctx context.Context, l Literal, vs []*Visa, f JWTVerifier) error {
glog.V(1).Info("checkLiteral")
for _, v := range vs {
if err := CheckCondition(l, v.Data().Assertion); err != nil {
glog.V(1).Infof("CheckCondition failed: %v", err)
continue
}
if err := f(ctx, string(v.JWT()), v.Data().Issuer, v.JKU()); err != nil {
glog.V(1).Infof("JWT verification failed: %v", err)
continue
}
return nil
}
return fmt.Errorf("insufficient visas")
}
// Condition represnet a GA4GH Passport Visa Condition.
// http://bit.ly/ga4gh-passport-v1#conditions
type Condition struct {
// Type http://bit.ly/ga4gh-passport-v1#type
Type Type `json:"type,omitempty"`
// Value http://bit.ly/ga4gh-passport-v1#pattern-matching
Value Pattern `json:"value,omitempty"`
// Source http://bit.ly/ga4gh-passport-v1#source
Source Pattern `json:"source,omitempty"`
// By http://bit.ly/ga4gh-passport-v1#by
By Pattern `json:"by,omitempty"`
}
// CheckCondition checks if a Visa satisfies a Condition.
// We use Visa because we would also need to verify the Visa.
// https://bit.ly/ga4gh-passport-v1#conditions
func CheckCondition(c Condition, a Assertion) error {
glog.V(1).Info("CheckCondition")
if c.Type == "" {
return fmt.Errorf("Condition must specifiy Type")
}
if c.Type != a.Type {
return fmt.Errorf("Type mismatch: %q %q", c.Type, a.Type)
}
if err := MatchPatterns(Pattern(c.By), string(a.By)); err != nil {
return fmt.Errorf("By mismatch: %v", err)
}
if err := MatchPatterns(c.Source, string(a.Source)); err != nil {
return fmt.Errorf("Source mismatch: %v", err)
}
if err := MatchPatterns(c.Value, string(a.Value)); err != nil {
return fmt.Errorf("Value mismatch: %v", err)
}
return nil
}
func toConditionsProto(c Conditions) []*cpb.ConditionSet {
if len(c) == 0 {
return nil
}
out := []*cpb.ConditionSet{}
for _, cor := range c {
cs := &cpb.ConditionSet{
AllOf: []*cpb.Condition{},
}
for _, cand := range cor {
clause := &cpb.Condition{
Type: string(cand.Type),
Source: string(cand.Source),
Value: string(cand.Value),
By: string(cand.By),
}
cs.AllOf = append(cs.AllOf, clause)
}
out = append(out, cs)
}
return out
}
// TODO: add tests for this file.