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 ¶
- func GetClaimsScopes(r *http.Request) (scopes map[string]struct{})
- func GetScopes(r *http.Request) []string
- type Authorization
- type Claims
- type DiscoveryDoc
- type ExpandedResource
- type KcPermission
- type KcPermissionLogic
- type KcPolicyDecisionStrategy
- type KeySet
- type KeycloakOption
- type KeycloakProvider
- func (p KeycloakProvider) Authenticate(client *http.Client) (*httputil.ClientCreds, error)
- func (p *KeycloakProvider) CreatePermissionForResource(resourceID string, perm *KcPermission) (permissionID string, err error)
- func (p KeycloakProvider) CreatePermissionTicket(resourceID string, scopes ...string) (string, error)
- func (p *KeycloakProvider) CreateResource(request *Resource) (response *ExpandedResource, err error)
- func (p *KeycloakProvider) Credentials() (issuer, clientID, clientSecret string)
- func (p *KeycloakProvider) DeletePermission(id string) (err error)
- func (p KeycloakProvider) DeleteResource(id string) (err error)
- func (p KeycloakProvider) GetResource(id string) (resource *ExpandedResource, err error)
- func (p *KeycloakProvider) ListPermissions(urlQuery url.Values) (perms []KcPermission, err error)
- func (p KeycloakProvider) ListResources(urlQuery url.Values) (ids []string, err error)
- func (p *KeycloakProvider) UpdatePermission(id string, perm *KcPermission) (err error)
- func (p KeycloakProvider) UpdateResource(id string, resource *Resource) (err error)
- func (p KeycloakProvider) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
- func (p *KeycloakProvider) WWWAuthenticateDirectives() WWWAuthenticateDirectives
- type Manager
- type ManagerOptions
- type Operation
- type Path
- type Permission
- type Provider
- type Resource
- type ResourceOwner
- type ResourceStore
- type ResourceTemplate
- type ResourceType
- type Scope
- type Security
- type WWWAuthenticateDirectives
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GetClaimsScopes ¶ added in v0.3.0
GetClaimsScopes check whether current RPT claims has specified scopes for the current resource
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"` }
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 (*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 (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 (*KeycloakProvider) UpdatePermission ¶
func (p *KeycloakProvider) UpdatePermission(id string, perm *KcPermission) (err error)
func (KeycloakProvider) UpdateResource ¶
func (KeycloakProvider) VerifySignature ¶
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
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 // 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 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
type Permission ¶
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 ¶
GetResource returns an uma Resource if one is found by Manager.Middleware
type ResourceOwner ¶
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