2023-02-06 22:43:50 +00:00
|
|
|
// Copyright 2023 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
// Package idtransform defines upstream-to-downstream identity transformations which could be
|
|
|
|
// implemented using various approaches or languages.
|
|
|
|
package idtransform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TransformationResult is the result of evaluating a transformation against some inputs.
|
|
|
|
type TransformationResult struct {
|
|
|
|
Username string // the new username for an allowed auth
|
|
|
|
Groups []string // the new group names for an allowed auth
|
|
|
|
AuthenticationAllowed bool // when false, disallow this authentication attempt
|
|
|
|
RejectedAuthenticationMessage string // should be set when AuthenticationAllowed is false
|
|
|
|
}
|
|
|
|
|
|
|
|
// IdentityTransformation is an individual identity transformation which can be evaluated.
|
|
|
|
type IdentityTransformation interface {
|
|
|
|
Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TransformationPipeline is a list of identity transforms, which can be evaluated in order against some given input
|
|
|
|
// values.
|
|
|
|
type TransformationPipeline struct {
|
|
|
|
transforms []IdentityTransformation
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTransformationPipeline creates an empty TransformationPipeline.
|
|
|
|
func NewTransformationPipeline() *TransformationPipeline {
|
|
|
|
return &TransformationPipeline{transforms: []IdentityTransformation{}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AppendTransformation adds a transformation to the end of the list of transformations for this pipeline.
|
|
|
|
// This is not thread-safe, so be sure to add all transformations from a single goroutine before using Evaluate
|
|
|
|
// from multiple goroutines.
|
|
|
|
func (p *TransformationPipeline) AppendTransformation(t IdentityTransformation) {
|
|
|
|
p.transforms = append(p.transforms, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Evaluate runs the transformation pipeline for a given input identity. It returns a potentially transformed or
|
|
|
|
// rejected identity, or an error. If any transformation in the list rejects the authentication, then the list is
|
|
|
|
// short-circuited but no error is returned. Only unexpected errors are returned as errors. This is safe to call
|
|
|
|
// from multiple goroutines.
|
|
|
|
func (p *TransformationPipeline) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
|
2023-06-07 21:22:02 +00:00
|
|
|
if groups == nil {
|
|
|
|
groups = []string{}
|
|
|
|
}
|
2023-02-06 22:43:50 +00:00
|
|
|
accumulatedResult := &TransformationResult{
|
|
|
|
Username: username,
|
|
|
|
Groups: groups,
|
|
|
|
AuthenticationAllowed: true,
|
|
|
|
}
|
|
|
|
for i, transform := range p.transforms {
|
2023-06-07 21:22:02 +00:00
|
|
|
var err error
|
2023-02-06 22:43:50 +00:00
|
|
|
accumulatedResult, err = transform.Evaluate(ctx, accumulatedResult.Username, accumulatedResult.Groups)
|
|
|
|
if err != nil {
|
|
|
|
// There was an unexpected error evaluating a transformation.
|
|
|
|
return nil, fmt.Errorf("identity transformation at index %d: %w", i, err)
|
|
|
|
}
|
|
|
|
if !accumulatedResult.AuthenticationAllowed {
|
|
|
|
// Auth has been rejected by a policy. Stop evaluating the rest of the transformations.
|
|
|
|
return accumulatedResult, nil
|
|
|
|
}
|
|
|
|
if strings.TrimSpace(accumulatedResult.Username) == "" {
|
|
|
|
return nil, fmt.Errorf("identity transformation returned an empty username, which is not allowed")
|
|
|
|
}
|
2023-06-07 21:22:02 +00:00
|
|
|
if accumulatedResult.Groups == nil {
|
|
|
|
return nil, fmt.Errorf("identity transformation returned a null list of groups, which is not allowed")
|
|
|
|
}
|
2023-02-06 22:43:50 +00:00
|
|
|
}
|
|
|
|
// There were no unexpected errors and no policy which rejected auth.
|
|
|
|
return accumulatedResult, nil
|
|
|
|
}
|