uma

package module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 16, 2022 License: MIT Imports: 10 Imported by: 3

README

uma-codegen

This command line tool reads OpenAPI spec and generates UMA middleware compatible with Go's http package.

Documentation

Overview

Package uma extends the OpenAPI spec to describe UMA resources and scopes in an API. It can generate an http middleware from an OpenAPI spec in order to enforce UMA permissions.

Get started

1. Define resource types

An UMA resource type contains fields that tend to stay the same for resources of the same type. Define resource types by adding `x-uma-resource-types` section to the root level of the spec e.g.

x-uma-resource-types:
  https://www.example.com/rsrcs/users:
    description: list of users
    iconUri: https://www.example.com/rsrcs/users/icon.png
    resourceScopes:
      - read
      - write
  https://www.example.com/rsrcs/user:
    description: a user
    iconUri: https://www.example.com/rsrcs/user/icon.png
    resourceScopes:
      - read
      - write

2. Enable UMA in security schemes

UMA access control only work with oauth2 or openIdConnect security scheme. To enable, add the key `x-uma-enabled` to the security scheme:

securitySchemes:
  oidc:
    type: openIdConnect
    openIdConnectUrl: /.well-known/openid-configuration
    x-uma-enabled: true

3. Define resources

UMA resources can be defined at the root level or at individual paths. Root level resource represents the default resource at all paths. Path level resource overrides root-level resource. e.g.

x-uma-resource:
  type: https://www.example.com/rsrcs/users
  name: Users
paths:
  ...
  /{id}:
    x-uma-resource:
      type: https://www.example.com/rsrcs/user
      name: User {id}

Each resource object has 2 keys: type and name. Type must be one of the types defined earlier. Name is the name template string that can be rendered using path parameters.

4. Define scopes

UMA scopes are simply oauth2 and openIdConnect scopes. They will work as UMA scopes as long as the security scheme is uma-enabled:

security:
  - oidc: [read]
paths:
  /{id}:
    put:
      security:
        - oidc: [write]

5. Generate code

This package can generate relevant go code from the OpenAPI spec:

go install github.com/pckhoi/uma/uma-codegen
uma-codegen openapi.yaml mypackage -o uma.gen.go

The generated code will work with any server compatible with the net/http package. It can works by itself or works along side other generated codes such as those generated by github.com/deepmap/oapi-codegen

6. Use the generated code

// create a new UMA provider
keycloakIssuer := "http://localhost:8080/realms/test-realm"
provider, _ := uma.NewKeycloakProvider(
	issuer, clientID, clientSecret,
	oidc.NewRemoteKeySet(context.Background(), issuer+"/protocol/openid-connect/certs"),
	logger,
)
// create a new UMA manager
umaManager := mypackage.UMAManager(uma.ManagerOptions{
	GetBaseURL: func(r *http.Request) url.URL {
		return url.URL{
			Scheme: "http",
			Host:   "localhost:" + port,
			Path:   "/users",
		}
	},
	GetProvider: func(r *http.Request) uma.Provider {
		return provider
	},
	// rs is any object that satisfy ResourceStore interface
	ResourceStore: rs,
}, logger)
s := &http.Server{}
sm := http.NewServeMux()
// apply the middleware, which enforces UMA permissions according to spec
s.Handler = umaManager.Middleware(sm)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetClaimsScopes added in v0.3.0

func GetClaimsScopes(r *http.Request) (scopes map[string]struct{})

GetClaimsScopes check whether current RPT claims has specified scopes for the current resource

func GetScopes

func GetScopes(r *http.Request) []string

GetScopes returns uma scopes if they are found by Manager.Middleware

Types

type Authorization

type Authorization struct {
	Permissions []Permission `json:"permissions,omitempty"`
}

type Claims

type Claims struct {
	Authorization     *Authorization `json:"authorization,omitempty"`
	Email             string         `json:"email,omitempty"`
	Name              string         `json:"name,omitempty"`
	GivenName         string         `json:"given_name,omitempty"`
	FamilyName        string         `json:"family_name,omitempty"`
	PreferredUsername string         `json:"preferred_username,omitempty"`
	EmailVerified     bool           `json:"email_verified,omitempty"`
	Aud               string         `json:"aud,omitempty"`
	Sid               string         `json:"sid,omitempty"`
	Jti               string         `json:"jti,omitempty"`
	Exp               int            `json:"exp,omitempty"`
	Nbf               int            `json:"nbf,omitempty"`
	Iat               int            `json:"iat,omitempty"`
	Sub               string         `json:"sub,omitempty"`
	Typ               string         `json:"typ,omitempty"`
	Azp               string         `json:"azp,omitempty"`
}

func GetClaims

func GetClaims(r *http.Request) *Claims

GetClaims returns claims from Requesting Party Token

func (*Claims) IsValid

func (tok *Claims) IsValid(resourceID string, disableTokenExpirationCheck bool, scopes []string, logger logr.Logger) bool

type DiscoveryDoc

type DiscoveryDoc struct {
	TokenEndpoint                string `json:"token_endpoint,omitempty"`
	TokenIntrospectionEndpoint   string `json:"token_introspection_endpoint,omitempty"`
	ResourceRegistrationEndpoint string `json:"resource_registration_endpoint,omitempty"`
	PermissionEndpoint           string `json:"permission_endpoint,omitempty"`
	PolicyEndpoint               string `json:"policy_endpoint,omitempty"`
}

type ExpandedResource

type ExpandedResource struct {
	ID             string  `json:"_id,omitempty"`
	Name           string  `json:"name,omitempty"`
	Type           string  `json:"type,omitempty"`
	Description    string  `json:"description,omitempty"`
	IconUri        string  `json:"icon_uri,omitempty"`
	ResourceScopes []Scope `json:"resource_scopes,omitempty"`
	Scopes         []Scope `json:"scopes,omitempty"`

	// Keycloak only fields
	Owner              *ResourceOwner `json:"owner,omitempty"`
	OwnerManagedAccess bool           `json:"ownerManagedAccess,omitempty"`
	URIs               []string       `json:"uris,omitempty"`
}

type KcPermission

type KcPermission struct {
	ID               string                   `json:"id,omitempty"`
	Name             string                   `json:"name"`
	Type             string                   `json:"type,omitempty"`
	Description      string                   `json:"description,omitempty"`
	Logic            KcPermissionLogic        `json:"logic,omitempty"`
	DecisionStrategy KcPolicyDecisionStrategy `json:"decisionStrategy,omitempty"`
	Scopes           []string                 `json:"scopes,omitempty"`
	Owner            string                   `json:"owner,omitempty"`
	Roles            []string                 `json:"roles,omitempty"`
	Groups           []string                 `json:"groups,omitempty"`
	Clients          []string                 `json:"clients,omitempty"`
}

type KcPermissionLogic

type KcPermissionLogic string
const (
	KcPositive KcPermissionLogic = "POSITIVE"
	KcNegative KcPermissionLogic = "NEGATIVE"
)

type KcPolicyDecisionStrategy

type KcPolicyDecisionStrategy string
const (
	KcUnanimous KcPolicyDecisionStrategy = "UNANIMOUS"
)

type KeySet

type KeySet interface {
	// VerifySignature parses the JSON web token, verifies the signature, and returns
	// the raw payload. Header and claim fields are validated by other parts of the
	// package. For example, the KeySet does not need to check values such as signature
	// algorithm, issuer, and audience since the IDTokenVerifier validates these values
	// independently.
	//
	// If VerifySignature makes HTTP requests to verify the token, it's expected to
	// use any HTTP client associated with the context through ClientContext.
	VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
}

KeySet mirrors oidc.KeySet interface. Learn more at https://pkg.go.dev/github.com/coreos/go-oidc/v3/oidc#KeySet

type KeycloakOption added in v0.2.0

type KeycloakOption func(kp *KeycloakProvider)

func WithKeycloakClient added in v0.2.0

func WithKeycloakClient(client *http.Client) KeycloakOption

WithKeycloakClient directs KeycloakProvider to use a custom http client

func WithKeycloakOwnerManagedAccess added in v0.2.0

func WithKeycloakOwnerManagedAccess() KeycloakOption

WithKeycloakOwnerManagedAccess sets ownerManagedAccess for each resource to true during resource creation

type KeycloakProvider

type KeycloakProvider struct {
	ClientID string
	// contains filtered or unexported fields
}

func NewKeycloakProvider

func NewKeycloakProvider(issuer, clientID, clientSecret string, keySet KeySet, logger logr.Logger, opts ...KeycloakOption) (p *KeycloakProvider, err error)

func (KeycloakProvider) Authenticate

func (p KeycloakProvider) Authenticate(client *http.Client) (*httputil.ClientCreds, error)

func (*KeycloakProvider) CreatePermissionForResource

func (p *KeycloakProvider) CreatePermissionForResource(resourceID string, perm *KcPermission) (permissionID string, err error)

func (KeycloakProvider) CreatePermissionTicket

func (p KeycloakProvider) CreatePermissionTicket(resourceID string, scopes ...string) (string, error)

func (*KeycloakProvider) CreateResource

func (p *KeycloakProvider) CreateResource(request *Resource) (response *ExpandedResource, err error)

func (*KeycloakProvider) Credentials added in v0.3.0

func (p *KeycloakProvider) Credentials() (issuer, clientID, clientSecret string)

func (*KeycloakProvider) DeletePermission

func (p *KeycloakProvider) DeletePermission(id string) (err error)

func (KeycloakProvider) DeleteResource

func (p KeycloakProvider) DeleteResource(id string) (err error)

func (KeycloakProvider) GetResource

func (p KeycloakProvider) GetResource(id string) (resource *ExpandedResource, err error)

func (*KeycloakProvider) ListPermissions

func (p *KeycloakProvider) ListPermissions(urlQuery url.Values) (perms []KcPermission, err error)

func (KeycloakProvider) ListResources

func (p KeycloakProvider) ListResources(urlQuery url.Values) (ids []string, err error)

func (*KeycloakProvider) UpdatePermission

func (p *KeycloakProvider) UpdatePermission(id string, perm *KcPermission) (err error)

func (KeycloakProvider) UpdateResource

func (p KeycloakProvider) UpdateResource(id string, resource *Resource) (err error)

func (KeycloakProvider) VerifySignature

func (p KeycloakProvider) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)

func (*KeycloakProvider) WWWAuthenticateDirectives

func (p *KeycloakProvider) WWWAuthenticateDirectives() WWWAuthenticateDirectives

type Manager added in v0.2.0

type Manager struct {
	// contains filtered or unexported fields
}

func New added in v0.2.0

func New(
	opts ManagerOptions,
	types map[string]ResourceType,
	securitySchemes []string,
	defaultResource *ResourceTemplate,
	defaultSecurity Security,
	paths []Path,
	logger logr.Logger,
) *Manager

func (*Manager) AskForTicket added in v0.3.0

func (m *Manager) AskForTicket(w http.ResponseWriter, r *http.Request)

func (*Manager) Middleware added in v0.2.0

func (m *Manager) Middleware(next http.Handler) http.Handler

Middleware is a http middleware that does the following things:

  • Find the resource and required scopes based on request URL and method
  • Register the resource with the provider if it's not already registered
  • If a token isn't included or if the token does not have permission, get an UMA ticket from the provider, returns the UMA ticket in WWW-Authenticate header.
  • If a token is included and valid, set resource, scopes, and claims in the request context. They can be retrieved with GetResource, GetScopes, and GetClaims respectively.

func (*Manager) RegisterResourceAt added in v0.2.0

func (m *Manager) RegisterResourceAt(r *http.Request, rs ResourceStore, p Provider, baseURL url.URL, path string) (rsc *Resource, err error)

RegisterResourceAt finds resource at path. If one is found, it registers the resource with the provider. If a resource is not found, both rsc and err are nil.

type ManagerOptions added in v0.2.0

type ManagerOptions struct {
	// GetBaseURL returns the base url of the covered api. It is typically the "url" of the matching
	// server entry in openapi spec. It should have this format: "{SCHEME}://{PUBLIC_HOSTNAME}{ANY_BASE_PATH}"
	GetBaseURL func(r *http.Request) url.URL

	// GetProvider returns the provider info given the request. It allows you to use different UMA
	// providers for different requests if you so wish
	GetProvider func(r *http.Request) Provider

	// ResourceStore persistently stores resource name and id. This tells the middleware which resource
	// is already registered so it doesn't have to be registered again.
	GetResourceStore func(r *http.Request) ResourceStore

	// Includes scopes in permission ticket in order to be granted specific scopes (the currently needed scopes)
	// on a resource. If scopes are not included, the authorization server might decides to grant all scopes
	// on the request resource.
	IncludeScopesInPermissionTicket bool

	// Skip token expiration check during token validation. This is only useful during testing, don't set
	// to true in production.
	DisableTokenExpirationCheck bool

	// GetResourceName if defined, must return the correct name of the resource. The preferred way to set resource
	// name is to define name template for the resource (x-uma-resource.name) in the OpenAPI spec. This method
	// should only be used when that is not possible.
	GetResourceName func(r *http.Request, rsc Resource) string

	// CustomEnforce handler if defined, cut the UMA provider out of the flow entirely, and allows deciding access
	// with custom logic. If the handler return true, allow the request to come through. Otherwise, responds with 401.
	CustomEnforce func(r *http.Request, resource Resource, scopes []string) bool

	// EditUnauthorizedResponse allows you to add additional headers and write custom body for 401 unauthorized
	// responses. Whatever you do, don't touch the "WWW-Authenticate" header as that is how the ticket is
	// transferred. Also make sure to write headers with status code 401.
	EditUnauthorizedResponse func(rw http.ResponseWriter)

	// AnonymousScopes is invoked when the user is unauthenticated. It is given the resource object that is being
	// accessed and should return the scopes available to anonymous users. If the scopes are sufficient, the user
	// is allowed to access. Otherwise an UMA ticket is created and returned in 401 response as usual.
	AnonymousScopes func(r *http.Request, resource Resource) (scopes []string)
}

type Operation

type Operation struct {
	Security Security
}

type Path

type Path struct {
	// contains filtered or unexported fields
}

func NewPath

func NewPath(pathTmpl string, rscTmpl *ResourceTemplate, operations map[string]Operation) Path

func (*Path) FindScopes added in v0.2.0

func (p *Path) FindScopes(securitySchemes map[string]struct{}, method string) (scopes []string)

func (*Path) MatchPath

func (p *Path) MatchPath(types map[string]ResourceType, baseURL, path string) (rsc *Resource, match bool)

type Permission

type Permission struct {
	Rsid   string   `json:"rsid,omitempty"`
	Rsname string   `json:"rsname,omitempty"`
	Scopes []string `json:"scopes,omitempty"`
}

type Provider

type Provider interface {
	KeySet

	// Authenticate authenticates client and get an access token for permission api
	Authenticate(client *http.Client) (*httputil.ClientCreds, error)

	// CreateResource creates resource
	CreateResource(request *Resource) (response *ExpandedResource, err error)

	// GetResource gets resource by id
	GetResource(id string) (resource *ExpandedResource, err error)

	// UpdateResource updates resource by id
	UpdateResource(id string, resource *Resource) (err error)

	// DeleteResource delete resource by id
	DeleteResource(id string) (err error)

	// ListResources lists resources. You can add custom query parameters with urlQuery
	ListResources(urlQuery url.Values) (ids []string, err error)

	// CreatePermissionTicket creates a permission ticket based on resourceID and
	// optional scopes
	CreatePermissionTicket(resourceID string, scopes ...string) (string, error)

	WWWAuthenticateDirectives() WWWAuthenticateDirectives
}

type Resource

type Resource struct {
	ResourceType

	// ID is the identifier defined by the authorization server
	ID string `json:"_id,omitempty"`

	// Name is the URI where the resource was detected
	Name string `json:"name,omitempty"`

	// Keycloak only fields
	Owner              string `json:"owner,omitempty"`
	OwnerManagedAccess bool   `json:"ownerManagedAccess,omitempty"`
	URI                string `json:"uri,omitempty"`
}

Resource describes an UMA resource. This object when rendered as JSON, can be used directly as request payload to create the resource.

func GetResource

func GetResource(r *http.Request) *Resource

GetResource returns an uma Resource if one is found by Manager.Middleware

type ResourceOwner

type ResourceOwner struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
}

type ResourceStore

type ResourceStore interface {
	// Set resource id associated with given name
	Set(name, id string) error

	// Get resource id associated with given name. If this function returns
	// empty string, the Manager creates a new resource, registers it with the
	// provider, and persists the id using Set
	Get(name string) (id string, err error)
}

ResourceStore persists resource name and id as registered with the provider

type ResourceTemplate

type ResourceTemplate struct {
	// contains filtered or unexported fields
}

func NewResourceTemplate

func NewResourceTemplate(rscType, rscNameTmpl string) *ResourceTemplate

func (*ResourceTemplate) CreateResource

func (t *ResourceTemplate) CreateResource(types map[string]ResourceType, uri string, params map[string]string) (rsc *Resource)

type ResourceType

type ResourceType struct {
	Type           string   `json:"type,omitempty"`
	Description    string   `json:"description,omitempty"`
	IconUri        string   `json:"icon_uri,omitempty"`
	ResourceScopes []string `json:"resource_scopes,omitempty"`
}

ResourceType describes and provides defaults for an UMA resource. Learn more at https://docs.kantarainitiative.org/uma/wg/oauth-uma-federated-authz-2.0-09.html#resource-set-desc

type Scope

type Scope struct {
	ID   string `json:"id,omitempty"`
	Name string `json:"name,omitempty"`
}

type Security

type Security []map[string][]string

type WWWAuthenticateDirectives

type WWWAuthenticateDirectives struct {
	Realm string
	AsUri string
}

Directories

Path Synopsis
pkg
rp

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL