Documentation
¶
Overview ¶
Package permio is the Go SDK for perms.io, a permissions (RBAC) service.
A Client wraps the generated gRPC clients with API key auth, per-attempt timeouts, retries for idempotent reads, typed errors (Error) and helpers for checks, grants, groups and paginated listings:
client, err := permio.New(permio.Options{APIKey: os.Getenv("PERMIO_API_KEY"), Project: "my-app"})
if err != nil { ... }
defer client.Close()
resource, err := permio.Resource("docs", docID)
if err != nil { ... }
ok, err := client.Check(ctx, userID, "docs.read", resource)
The generated clients stay reachable through Client.Permissions and Client.Organisations, with the same auth, timeouts, retries and errors.
Example ¶
package main
import (
"context"
"fmt"
"log"
"os"
permio "github.com/PrivateJAR/permio-go"
)
func main() {
client, err := permio.New(permio.Options{APIKey: os.Getenv("PERMIO_API_KEY"), Project: "my-app"})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ok, err := client.Check(context.Background(), "user-123", "docs.read", permio.MustResource("docs", "42"))
if err != nil {
log.Fatal(err)
}
fmt.Println("allowed:", ok)
}
Output:
Index ¶
- Constants
- Variables
- func Collect[T any](seq iter.Seq2[T, error]) ([]T, error)
- func EncodeSegment(segment string) string
- func GroupPrincipal(slug string) string
- func IsIdempotent(fullMethod string) bool
- func IsNotFound(err error) bool
- func MustResource(segments ...string) string
- func Resource(segments ...string) (string, error)
- func ValidateGroupName(slug string) error
- func ValidateResourceURI(uri string) error
- type APIKeyAccess
- type APIKeyKind
- type AccessEntry
- type AccessScope
- type AccessScopes
- func (a *AccessScopes) Allows(uri string) bool
- func (a *AccessScopes) Exact() []string
- func (a *AccessScopes) HasPatterns() bool
- func (a *AccessScopes) IsEmpty() bool
- func (a *AccessScopes) Patterns() []string
- func (a *AccessScopes) Prefixes() []string
- func (a *AccessScopes) Regexp() *regexp.Regexp
- func (a *AccessScopes) RegexpSource() string
- type AccessVia
- type BatchResult
- type CheckRequest
- type CheckResult
- type Client
- func (c *Client) APIKeyKind() APIKeyKind
- func (c *Client) Assert(ctx context.Context, principal, permission, resource string) error
- func (c *Client) AssertAll(ctx context.Context, req CheckRequest) error
- func (c *Client) BatchCheck(ctx context.Context, principal string, permissions []string, ...) (map[string]bool, error)
- func (c *Client) BatchCheckDetailed(ctx context.Context, principal string, permissions []string, ...) ([]BatchResult, error)
- func (c *Client) Check(ctx context.Context, principal, permission, resource string) (bool, error)
- func (c *Client) CheckAll(ctx context.Context, principal string, permissions []string, resource string) (bool, error)
- func (c *Client) CheckAny(ctx context.Context, principal string, permissions []string, resource string) (bool, error)
- func (c *Client) CheckDetailed(ctx context.Context, req CheckRequest) (*CheckResult, error)
- func (c *Client) Close() error
- func (c *Client) Conn() grpc.ClientConnInterface
- func (c *Client) CreatePermission(ctx context.Context, name, description string) (*Permission, error)
- func (c *Client) CreateRole(ctx context.Context, name, description string, permissions []string) (*Role, error)
- func (c *Client) DeletePermission(ctx context.Context, name string) error
- func (c *Client) DeleteRole(ctx context.Context, id string) error
- func (c *Client) GetGrant(ctx context.Context, principal, resource string) (*Grant, error)
- func (c *Client) GetPermission(ctx context.Context, name string) (*Permission, error)
- func (c *Client) GetRole(ctx context.Context, id string) (*Role, error)
- func (c *Client) Grant(ctx context.Context, req GrantRequest) error
- func (c *Client) Groups() *Groups
- func (c *Client) ListAccess(ctx context.Context, principal, permission, under string) (*AccessScopes, error)
- func (c *Client) ListAssignments(ctx context.Context, principal string, opts *ListOptions) iter.Seq2[Grant, error]
- func (c *Client) ListPermissions(ctx context.Context, opts *ListOptions) iter.Seq2[Permission, error]
- func (c *Client) ListPrincipals(ctx context.Context, opts *ListOptions) iter.Seq2[string, error]
- func (c *Client) ListRoles(ctx context.Context, opts *ListOptions) iter.Seq2[Role, error]
- func (c *Client) Organisations() organisationv1.OrganisationServiceClient
- func (c *Client) Permissions() permissionsv1.PermissionsServiceClient
- func (c *Client) Project() string
- func (c *Client) Revoke(ctx context.Context, req GrantRequest) error
- func (c *Client) UpdatePermission(ctx context.Context, name, newName, description string) (*Permission, error)
- func (c *Client) UpdateRole(ctx context.Context, id, name, description string, permissions []string) (*Role, error)
- func (c *Client) WhoAmI(ctx context.Context) (*Identity, error)
- func (c *Client) WhoHasAccess(ctx context.Context, resource string, opts *WhoHasAccessOptions) ([]AccessEntry, error)
- func (c *Client) WithProject(project string) *Client
- type Error
- type Grant
- type GrantRequest
- type Group
- type GroupMember
- type Groups
- func (g *Groups) AddMembers(ctx context.Context, name string, principals []string) (*MembershipChange, error)
- func (g *Groups) Create(ctx context.Context, name, description string) (*Group, error)
- func (g *Groups) Delete(ctx context.Context, name string) error
- func (g *Groups) Get(ctx context.Context, name string) (*Group, error)
- func (g *Groups) List(ctx context.Context, opts *ListOptions) iter.Seq2[Group, error]
- func (g *Groups) ListForPrincipal(ctx context.Context, principal string, opts *ListOptions) iter.Seq2[Group, error]
- func (g *Groups) ListMembers(ctx context.Context, name string, opts *ListOptions) iter.Seq2[GroupMember, error]
- func (g *Groups) RemoveMembers(ctx context.Context, name string, principals []string) (*MembershipChange, error)
- func (g *Groups) Update(ctx context.Context, name, description string) (*Group, error)
- type Identity
- type Kind
- type ListOptions
- type MembershipChange
- type MissingPermission
- type Options
- type Permission
- type PrincipalKind
- type RetryOptions
- type Role
- type RoleRef
- type WhoHasAccessOptions
Examples ¶
Constants ¶
const ( ScopeSubtree = "subtree" ScopePattern = "pattern" ScopeExact = "exact" ScopeUnknown = "unknown" )
Scope kinds.
const ( // DefaultEndpoint is the production API. DefaultEndpoint = "api.perms.io:443" // DevEndpoint is the development API. DevEndpoint = "dev-api.perms.io:443" // DefaultTimeout is the default per-attempt timeout. DefaultTimeout = 10 * time.Second // EnvAPIKey is read when Options.APIKey is empty. EnvAPIKey = "PERMIO_API_KEY" )
const ( // APIKeyLength is the length of an opaque "perms_" API key. APIKeyLength = len(apiKeyPrefix) + apiKeyRandomLength + apiKeyChecksumLength )
API key format, mirroring the server: "perms_" + 32 random base62 characters + 6 base62 characters encoding the CRC32 (IEEE) of those 32, 44 in total.
const GroupPrefix = "group:"
GroupPrefix starts the principal ID of every group.
const MaxBatch = 100
MaxBatch is the most resources or permissions one Check or BatchCheck call accepts, and the most principals Groups.AddMembers and Groups.RemoveMembers accept.
const MaxPageSize = 100
MaxPageSize is the largest page the API accepts (PaginationRequest.limit is 1..100).
const MaxResourceURILength = 2048
MaxResourceURILength is the longest resource URI the API accepts, in bytes.
const WildcardSegment = "_"
WildcardSegment is the "_" segment: in a stored grant URI or a pattern scope it matches exactly one segment.
Variables ¶
var ( ErrConfig error = KindConfig ErrAuth error = KindAuth ErrPermission error = KindPermission ErrValidation error = KindValidation ErrNotFound error = KindNotFound ErrAlreadyExists error = KindAlreadyExists ErrTimeout error = KindTimeout ErrRateLimited error = KindRateLimited ErrDenied error = KindDenied )
Sentinels for errors.Is. Each is the matching Kind.
Functions ¶
func Collect ¶
Collect gathers every item of a listing into a slice, stopping at the first error:
roles, err := permio.Collect(client.ListRoles(ctx, nil))
func EncodeSegment ¶
EncodeSegment percent-encodes one resource URI segment. Only A-Z, a-z, 0-9 and "-_.~" are left as they are (like JavaScript's encodeURIComponent, but also encoding "!'()*", which the API rejects in segments).
func GroupPrincipal ¶
GroupPrincipal returns the principal ID of the group with this slug, "group:<slug>". Grant to it, or check it, like any other principal; every member of the group holds what the group is granted.
func IsIdempotent ¶
IsIdempotent reports whether the SDK retries the RPC with this full gRPC method name ("/package.Service/Method").
func IsNotFound ¶
IsNotFound reports whether err is a NotFound error.
func MustResource ¶
MustResource is like Resource but panics if the URI is invalid. Use it for segments known to be non-empty, such as literals.
func Resource ¶
Resource builds a resource URI from raw segments, percent-encoding each one so that IDs containing "/", spaces or other reserved characters stay a single segment:
uri, err := permio.Resource("projects", projectID, "docs", "a/b") // "/projects/p1/docs/a%2Fb"
Build URIs the same way when granting and when checking, or they will not match. It fails with KindValidation when given no segments (rather than returning "/", which would cover every resource in a grant), an empty segment, or segments that make the URI too long.
func ValidateGroupName ¶
ValidateGroupName checks a group slug: 1-64 lower-case letters, digits and hyphens. Errors are of kind KindValidation.
func ValidateResourceURI ¶
ValidateResourceURI checks the basic shape the API accepts: a leading "/", no empty segments (so no "//" or trailing "/"), no whitespace or control characters, no regex metacharacters in segments, at most 2048 bytes. Errors are of kind KindValidation.
Types ¶
type APIKeyAccess ¶
type APIKeyAccess string
APIKeyAccess is an API key's access level on its project.
const ( // APIKeyAccessCheck: checks and reads only. APIKeyAccessCheck APIKeyAccess = "check" // APIKeyAccessFull: also writes, grant and revoke. APIKeyAccessFull APIKeyAccess = "full" // APIKeyAccessUnknown: the key's rights were changed by hand. APIKeyAccessUnknown APIKeyAccess = "unknown" )
type APIKeyKind ¶
type APIKeyKind string
APIKeyKind says which form an API key has.
const ( // APIKeyOpaque is a "perms_" key from the dashboard. APIKeyOpaque APIKeyKind = "opaque" // APIKeyJWT is a legacy JWT key. APIKeyJWT APIKeyKind = "jwt" )
func ValidateAPIKey ¶
func ValidateAPIKey(key string) (APIKeyKind, error)
ValidateAPIKey checks that key looks like a perms.io API key and returns its kind. Opaque "perms_" keys are checked for length, alphabet and checksum, so a typo is caught before any request is made; legacy JWT keys only for their three-part shape. Only the API can say whether a key exists and is valid. Errors are of kind KindConfig.
type AccessEntry ¶
type AccessEntry struct {
Principal string `json:"principal"`
IsGroup bool `json:"is_group"`
// ViaGroup is the group slug through which Principal has access, for
// entries added by expand_groups; empty for the principal's own grants.
ViaGroup string `json:"via_group"`
Via []AccessVia `json:"via"`
}
AccessEntry is one principal with access to a resource.
type AccessScope ¶
type AccessScope struct {
// URI is a resource URI (subtree, exact) or a URI pattern with "_" segments (pattern).
URI string `json:"uri"`
// Kind is "subtree" (the URI and everything below), "pattern" (URIs whose
// leading segments match, "_" matching one segment, and everything below),
// "exact" (reserved by the API) or "unknown".
Kind string `json:"kind"`
}
AccessScope is one place where a principal holds a permission.
type AccessScopes ¶
type AccessScopes struct {
Under string `json:"under"`
// CoversAll is true when the principal holds the permission on Under itself,
// and so on everything below it.
CoversAll bool `json:"covers_all"`
Scopes []AccessScope `json:"scopes"`
}
AccessScopes answers "where may this principal use this permission under Under?".
func (*AccessScopes) Allows ¶
func (a *AccessScopes) Allows(uri string) bool
Allows reports whether the scopes cover uri. A URI outside Under was not asked about, so false for it means "unknown", not "denied".
func (*AccessScopes) Exact ¶
func (a *AccessScopes) Exact() []string
Exact returns the exact scopes' URIs. The API does not return exact scopes today.
func (*AccessScopes) HasPatterns ¶
func (a *AccessScopes) HasPatterns() bool
HasPatterns reports whether some scope is a pattern, so Prefixes alone does not cover everything.
func (*AccessScopes) IsEmpty ¶
func (a *AccessScopes) IsEmpty() bool
IsEmpty reports whether the principal has no access anywhere under Under.
func (*AccessScopes) Patterns ¶
func (a *AccessScopes) Patterns() []string
Patterns returns the pattern scopes' URIs (with "_" segments).
func (*AccessScopes) Prefixes ¶
func (a *AccessScopes) Prefixes() []string
Prefixes returns the subtree scopes' URIs, dropping any already covered by a shorter one. A prefix p stands for p and everything below it: match `path = p OR starts_with(path, p || '/')` (PostgreSQL; elsewhere use a literal prefix test such as `left(path, length(p) + 1) = p || '/'`). Avoid `LIKE p || '/%'`: "_" and "%" are LIKE wildcards and may occur in URIs.
func (*AccessScopes) Regexp ¶
func (a *AccessScopes) Regexp() *regexp.Regexp
Regexp is AccessScopes.RegexpSource compiled.
func (*AccessScopes) RegexpSource ¶
func (a *AccessScopes) RegexpSource() string
RegexpSource returns one anchored regular expression that matches exactly the URIs Allows accepts, for stores that filter by regex (PostgreSQL "~", MongoDB $regex). With no scopes it matches nothing ("$.^"). The syntax is common to RE2, PCRE and POSIX ERE.
type AccessVia ¶
type AccessVia struct {
// Resource is the URI the grant was made on.
Resource string `json:"resource"`
// Inherited is true unless the grant was made on exactly the resource.
Inherited bool `json:"inherited"`
// Match is "exact", "ancestor", "wildcard" or "unknown".
Match string `json:"match"`
// Source is "role" or "direct".
Source string `json:"source"`
RoleID string `json:"role_id,omitempty"`
RoleName string `json:"role_name,omitempty"`
}
AccessVia is one grant (or one role within a grant) that gives access.
type BatchResult ¶
type BatchResult struct {
Resource string `json:"resource"`
// Allowed is true when the principal holds every requested permission on Resource.
Allowed bool `json:"allowed"`
// Missing is the requested permissions the principal lacks on Resource.
Missing []string `json:"missing"`
}
BatchResult is one resource's result from Client.BatchCheckDetailed.
type CheckRequest ¶
type CheckRequest struct {
// Principal is the principal (user, service, "group:<slug>"...) whose
// permissions are checked.
Principal string
// Resources are resource URIs; every one is checked. At most 100.
Resources []string
// Permissions are permission names. At most 100.
Permissions []string
}
CheckRequest is a check of several permissions on several resources.
type CheckResult ¶
type CheckResult struct {
// Allowed is true when the principal holds every permission on every resource.
Allowed bool `json:"allowed"`
// Missing lists what is missing per resource. Empty when allowed.
Missing []MissingPermission `json:"missing"`
}
CheckResult is the result of Client.CheckDetailed.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the perms.io API. It is safe for concurrent use.
func New ¶
New validates the options and creates a client. It does not connect until the first call. Errors are of kind KindConfig.
func (*Client) APIKeyKind ¶
func (c *Client) APIKeyKind() APIKeyKind
APIKeyKind reports whether the client's key is an opaque "perms_" key or a legacy JWT.
func (*Client) Assert ¶
Assert returns nil when principal holds permission on resource, and an Error of kind KindDenied (with Missing filled in) when it does not.
func (*Client) AssertAll ¶
func (c *Client) AssertAll(ctx context.Context, req CheckRequest) error
AssertAll returns nil when the principal holds every permission on every resource, and an Error of kind KindDenied when it does not.
func (*Client) BatchCheck ¶
func (c *Client) BatchCheck(ctx context.Context, principal string, permissions []string, resources []string) (map[string]bool, error)
BatchCheck checks each resource separately and returns, per resource URI, whether principal holds every one of permissions on it. Use it to filter a list you already have in memory. More than 100 resources are split into several calls.
func (*Client) BatchCheckDetailed ¶
func (c *Client) BatchCheckDetailed(ctx context.Context, principal string, permissions []string, resources []string) ([]BatchResult, error)
BatchCheckDetailed is BatchCheck returning one entry per distinct resource, in request order, with the missing permissions.
func (*Client) Check ¶
Check reports whether principal holds permission on resource. A denial is false with a nil error.
func (*Client) CheckAll ¶
func (c *Client) CheckAll(ctx context.Context, principal string, permissions []string, resource string) (bool, error)
CheckAll reports whether principal holds every one of permissions on resource.
func (*Client) CheckAny ¶
func (c *Client) CheckAny(ctx context.Context, principal string, permissions []string, resource string) (bool, error)
CheckAny reports whether principal holds at least one of permissions on resource. It makes a single Check call.
func (*Client) CheckDetailed ¶
func (c *Client) CheckDetailed(ctx context.Context, req CheckRequest) (*CheckResult, error)
CheckDetailed checks whether the principal holds every permission on every resource and returns what is missing. A denial is a normal result, not an error.
It fails with KindPermission if the API key lacks permission.check, and KindValidation for malformed URIs or empty inputs.
func (*Client) Close ¶
Close releases the connection the client dialled. Clients made with Client.WithProject share it; close only the original.
func (*Client) Conn ¶
func (c *Client) Conn() grpc.ClientConnInterface
Conn returns the wrapped connection, for other generated clients.
func (*Client) CreatePermission ¶
func (c *Client) CreatePermission(ctx context.Context, name, description string) (*Permission, error)
CreatePermission creates a permission, or returns the existing one: create is idempotent. If the permission already exists, a non-empty description that differs replaces the stored one (which needs permission.update on it); an empty description leaves it as it is.
func (*Client) CreateRole ¶
func (c *Client) CreateRole(ctx context.Context, name, description string, permissions []string) (*Role, error)
CreateRole creates a role and returns it with its ID, or returns the existing role with this name: create is idempotent. If the role already exists, a non-empty description or permission list that differs replaces the stored one (which needs role.update on it); empty ones leave it as it is.
func (*Client) DeletePermission ¶
DeletePermission deletes a permission and every grant of it. Deleting one that does not exist succeeds.
func (*Client) DeleteRole ¶
DeleteRole deletes a role by ID.
func (*Client) GetGrant ¶
GetGrant returns what is granted to principal on exactly resource (not inherited grants). The API returns role IDs only, so RoleRef.Name is empty.
func (*Client) GetPermission ¶
GetPermission returns one permission by name.
func (*Client) Grant ¶
func (c *Client) Grant(ctx context.Context, req GrantRequest) error
Grant grants permissions and roles to a principal on a resource, and so on every resource below it. Additive: what the principal already holds there is kept. Pass GroupPrincipal(slug) to grant to every member of a group.
Like every write, a grant is sent once and never retried automatically. It fails with KindValidation if a permission or role does not exist, and KindPermission unless the API key has full access.
Example ¶
package main
import (
"context"
"errors"
"log"
permio "github.com/PrivateJAR/permio-go"
)
func main() {
var client *permio.Client // from permio.New
ctx := context.Background()
// Grant to a group: every member holds the grant.
err := client.Grant(ctx, permio.GrantRequest{
Principal: permio.GroupPrincipal("editors"),
Resource: permio.MustResource("docs"),
Permissions: []string{"docs.read", "docs.write"},
})
if errors.Is(err, permio.ErrPermission) {
log.Fatal("this API key is check-only; grants need a full-access key")
}
}
Output:
func (*Client) ListAccess ¶
func (c *Client) ListAccess(ctx context.Context, principal, permission, under string) (*AccessScopes, error)
ListAccess answers "where may principal use permission at or below under?", following every page. Access through roles and groups counts. Use the result to filter lists in your database: CoversAll means no filter is needed, Prefixes gives path prefixes, RegexpSource a regex, and Allows tests a single URI.
The API key needs permission.check on under and principal.assignment.get on the principal.
Example ¶
package main
import (
"context"
"fmt"
"log"
permio "github.com/PrivateJAR/permio-go"
)
func main() {
var client *permio.Client // from permio.New
access, err := client.ListAccess(context.Background(), "user-123", "docs.read", "/docs")
if err != nil {
log.Fatal(err)
}
switch {
case access.CoversAll:
// No filter needed.
case !access.HasPatterns():
fmt.Println("path prefixes:", access.Prefixes())
default:
fmt.Println("regex:", access.RegexpSource())
}
}
Output:
func (*Client) ListAssignments ¶
func (c *Client) ListAssignments(ctx context.Context, principal string, opts *ListOptions) iter.Seq2[Grant, error]
ListAssignments iterates every grant made to principal, one per resource URI (GetAllAssignmentsForPrincipal). opts may be nil; Search is ignored.
func (*Client) ListPermissions ¶
func (c *Client) ListPermissions(ctx context.Context, opts *ListOptions) iter.Seq2[Permission, error]
ListPermissions iterates every permission in the project. opts may be nil.
func (*Client) ListPrincipals ¶
ListPrincipals iterates the IDs of every principal with a grant in the project, groups included as "group:<slug>". opts may be nil.
func (*Client) ListRoles ¶
ListRoles iterates every role in the project. opts may be nil.
Example ¶
package main
import (
"context"
"fmt"
"log"
permio "github.com/PrivateJAR/permio-go"
)
func main() {
var client *permio.Client // from permio.New
for role, err := range client.ListRoles(context.Background(), nil) {
if err != nil {
log.Fatal(err)
}
fmt.Println(role.ID, role.Name)
}
}
Output:
func (*Client) Organisations ¶
func (c *Client) Organisations() organisationv1.OrganisationServiceClient
Organisations returns the generated OrganisationService client, with auth, timeouts, retries and error mapping applied.
func (*Client) Permissions ¶
func (c *Client) Permissions() permissionsv1.PermissionsServiceClient
Permissions returns the generated PermissionsService client, with auth, timeouts, retries and error mapping applied. Use it for RPCs the helpers do not wrap; set ProjectName on requests yourself.
Example ¶
package main
import (
"context"
"fmt"
"log"
permio "github.com/PrivateJAR/permio-go"
permissionsv1 "github.com/PrivateJAR/permio-go/proto/permissions/v1"
)
func main() {
var client *permio.Client // from permio.New
// The generated client, with auth, timeouts, retries and typed errors.
res, err := client.Permissions().GetTotalRolesAndPermissionsForOrganisation(context.Background(),
&permissionsv1.GetTotalRolesAndPermissionsForOrganisationRequest{})
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
}
Output:
func (*Client) Revoke ¶
func (c *Client) Revoke(ctx context.Context, req GrantRequest) error
Revoke removes permissions and roles from a principal's grant on exactly req.Resource. Names the grant does not hold are ignored, so revoking twice is harmless. Grants on ancestor URIs are not touched, so the principal may still pass a check on the resource afterwards; WhoHasAccess shows where access comes from.
Like every write, a revoke is sent once and never retried automatically.
func (*Client) UpdatePermission ¶
func (c *Client) UpdatePermission(ctx context.Context, name, newName, description string) (*Permission, error)
UpdatePermission replaces a permission's description and, when newName is not empty, renames it. Renaming to a name already in use fails with KindAlreadyExists.
func (*Client) UpdateRole ¶
func (c *Client) UpdateRole(ctx context.Context, id, name, description string, permissions []string) (*Role, error)
UpdateRole replaces a role's name, description and permissions. Renaming to a name another role uses fails with KindAlreadyExists.
func (*Client) WhoAmI ¶
WhoAmI describes the credential this client uses: for an API key, its ID, its organisation, the project it belongs to and its access level. Useful to verify a key at startup. It needs no project.
func (*Client) WhoHasAccess ¶
func (c *Client) WhoHasAccess(ctx context.Context, resource string, opts *WhoHasAccessOptions) ([]AccessEntry, error)
WhoHasAccess answers "who has access to resource?", following every page (ListPrincipalsWithAccess). It lists the principals (groups included, as "group:<slug>") with a grant on resource, an ancestor of it, or a "_" wildcard URI matching it, ordered by principal ID, with the grants that give the access. opts may be nil.
The API key needs principal.list and principal.assignment.list on the project.
func (*Client) WithProject ¶
WithProject returns a client that uses project instead of the default project. It shares c's connection.
type Error ¶
type Error struct {
// Kind classifies the error.
Kind Kind
// Code is the gRPC status code. Errors raised by the SDK itself use
// FailedPrecondition (config), PermissionDenied (denied) and
// InvalidArgument (its own input checks). It is never codes.OK.
Code codes.Code
// Message is the server's (or SDK's) message.
Message string
// Missing is set for KindPermission (what the API key lacks, with the
// server's fully qualified internal URIs) and KindDenied (what the
// principal lacks).
Missing []MissingPermission
// Principal is the checked principal, for KindDenied.
Principal string
// RetryAfter is the delay the server asked for (google.rpc.RetryInfo or a
// retry-after header), for KindRateLimited and sometimes KindUnavailable.
// Zero when the server gave none.
RetryAfter time.Duration
// Metadata holds the response headers and trailers sent with the error.
Metadata metadata.MD
// contains filtered or unexported fields
}
Error is the error returned by every Client call that fails, including calls made through the raw clients (Client.Permissions, Client.Organisations). Use errors.As to read its fields, or errors.Is with a Kind / Err* sentinel.
func (*Error) GRPCStatus ¶
GRPCStatus lets status.FromError and status.Code read the gRPC code. The code is never codes.OK: an *Error is always a failure.
type Grant ¶
type Grant struct {
Principal string `json:"principal"`
Resource string `json:"resource"`
Roles []RoleRef `json:"roles"`
Permissions []string `json:"permissions"`
}
Grant is the roles and permissions granted to one principal on exactly one resource URI (an "assignment").
type GrantRequest ¶
type GrantRequest struct {
// Principal is one of your principal IDs, or GroupPrincipal(slug) for a group.
Principal string
// Resource is the resource URI of the grant. A grant also covers every URI
// below it; a revoke applies to exactly this URI.
Resource string
// Permissions are permission names.
Permissions []string
// Roles are role IDs (see ListRoles).
Roles []string
}
GrantRequest says what to grant or revoke.
type Group ¶
type Group struct {
Name string `json:"name"`
Description string `json:"description"`
PrincipalID string `json:"principal_id"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
Group is a named set of principals. Grants to Group.PrincipalID ("group:<name>") apply to every member.
type GroupMember ¶
type GroupMember struct {
PrincipalID string `json:"principal_id"`
CreatedAt *time.Time `json:"created_at,omitempty"`
}
GroupMember is one member of a group.
type Groups ¶
type Groups struct {
// contains filtered or unexported fields
}
Groups manages groups of principals; get it with Client.Groups. A group is referenced as the principal "group:<slug>" (see GroupPrincipal): grant to it and every member holds the grant. Groups cannot contain groups.
Every method that takes a group accepts its slug ("editors") or its principal ID ("group:editors").
func (*Groups) AddMembers ¶
func (g *Groups) AddMembers(ctx context.Context, name string, principals []string) (*MembershipChange, error)
AddMembers adds up to MaxBatch (100) principals to a group in one write; more fail with KindValidation (split them yourself). Principals that were already members are reported in Unchanged. Group principals ("group:...") are rejected, because groups cannot be nested.
func (*Groups) Create ¶
Create creates a group. It fails with KindAlreadyExists if the project already has a group with this name.
func (*Groups) Delete ¶
Delete deletes a group, its memberships and every grant made to it. It succeeds whether or not the group existed.
func (*Groups) Get ¶
Get returns a group, or an error of kind KindNotFound.
func (*Groups) ListForPrincipal ¶
func (g *Groups) ListForPrincipal(ctx context.Context, principal string, opts *ListOptions) iter.Seq2[Group, error]
ListForPrincipal iterates the groups principal is a member of, ordered by name. opts may be nil; Search is ignored.
func (*Groups) ListMembers ¶
func (g *Groups) ListMembers(ctx context.Context, name string, opts *ListOptions) iter.Seq2[GroupMember, error]
ListMembers iterates a group's members, ordered by principal ID. opts may be nil; Search is ignored.
func (*Groups) RemoveMembers ¶
func (g *Groups) RemoveMembers(ctx context.Context, name string, principals []string) (*MembershipChange, error)
RemoveMembers removes up to MaxBatch (100) principals from a group in one write; more fail with KindValidation. Principals that were not members are reported in Unchanged.
type Identity ¶
type Identity struct {
// PrincipalID is the caller's principal ID; for an API key, the key's ID.
PrincipalID string `json:"principal_id"`
// OrganisationID is the caller's organisation. Empty for a user who has not joined one.
OrganisationID string `json:"organisation_id"`
// Kind is the kind of credential.
Kind PrincipalKind `json:"kind"`
// Project is the project an API key belongs to; it can only be used
// there. Empty for users.
Project string `json:"project,omitempty"`
// APIKeyAccess is set for API keys only.
APIKeyAccess APIKeyAccess `json:"api_key_access,omitempty"`
// Email is the user's email address, when the caller is a user with one.
Email string `json:"email,omitempty"`
}
Identity describes the caller, as returned by Client.WhoAmI.
type Kind ¶
type Kind string
Kind classifies an Error. Kinds are also errors, so callers can match them with errors.Is:
if errors.Is(err, permio.ErrNotFound) { ... }
const ( // KindConfig: the client is configured incorrectly, e.g. a malformed API // key or no project. KindConfig Kind = "config" // KindAuth: the API key was missing, unknown or revoked (gRPC Unauthenticated). KindAuth Kind = "auth" // KindPermission: the API key is valid but may not make this call (gRPC // PermissionDenied), e.g. a check-only key trying to write. This is about // the caller's rights; a principal failing a check is [KindDenied]. KindPermission Kind = "permission_denied" // KindValidation: the request was rejected as malformed (gRPC // InvalidArgument), or failed the SDK's own input checks. KindValidation Kind = "invalid_argument" // KindNotFound: the entity does not exist (gRPC NotFound). KindNotFound Kind = "not_found" // KindAlreadyExists: the entity already exists (gRPC AlreadyExists). KindAlreadyExists Kind = "already_exists" KindUnavailable Kind = "unavailable" // KindTimeout: the call did not finish in time (gRPC DeadlineExceeded). KindTimeout Kind = "timeout" // KindRateLimited: the organisation's rate limit is used up (gRPC // ResourceExhausted). [Error.RetryAfter] is the server's requested delay. KindRateLimited Kind = "rate_limited" // KindDenied: returned by the Assert methods when the principal does not // hold the permissions. A normal authorization outcome, not an API failure. KindDenied Kind = "denied" // KindOther: anything else, including cancellation by the caller. KindOther Kind = "error" )
type ListOptions ¶
type ListOptions struct {
// Search filters by name, where the listing supports it.
Search string
// PageSize is the number of items per request, 1..100. Default 100.
PageSize int
}
ListOptions configures a paginated listing.
type MembershipChange ¶
type MembershipChange struct {
// Changed are the principals added (or removed).
Changed []string `json:"changed"`
// Unchanged are the principals that were already members (or were not members).
Unchanged []string `json:"unchanged"`
}
MembershipChange is the result of adding or removing group members.
type MissingPermission ¶
type MissingPermission struct {
Resource string `json:"resource"`
Permissions []string `json:"permissions"`
}
MissingPermission lists permissions missing on one resource.
func ParseMissingPermissions ¶
func ParseMissingPermissions(msg string) []MissingPermission
ParseMissingPermissions extracts the missing-permission list from a PermissionDenied message, if it has one.
type Options ¶
type Options struct {
// APIKey is a "perms_" key from the dashboard, or a legacy JWT key. When
// empty, the PERMIO_API_KEY environment variable is used.
APIKey string
// Endpoint is the API's "host:port" ([DefaultEndpoint] when empty), or a
// URL such as "https://dev-api.perms.io". "http://" URLs dial without TLS,
// which is only allowed to a local host (see Insecure).
// Ignored when Conn is set.
Endpoint string
// Insecure dials without TLS. Only for local servers: the API key would be
// sent in clear, so New fails with [KindConfig] unless the endpoint's host
// is a loopback address, "localhost" or a name ending in ".localhost".
Insecure bool
// Project is the default project_name. Use [Client.WithProject] for others.
Project string
// Timeout is the per-attempt timeout. Zero means [DefaultTimeout]; a
// negative value disables it. Set an overall deadline with the context.
Timeout time.Duration
// Retry configures retries of idempotent reads. Writes are never retried.
Retry RetryOptions
// UserAgent is prepended to grpc-go's user agent.
UserAgent string
// DialOptions are appended to the default dial options.
DialOptions []grpc.DialOption
// Conn, when set, is used instead of dialling Endpoint, e.g. a bufconn
// connection in tests. Auth, timeouts, retries and error mapping still
// apply. The caller owns it: Close does not close it.
Conn grpc.ClientConnInterface
}
Options configures a Client. Only an API key is required.
type Permission ¶
Permission is a named action, e.g. "document.read".
type PrincipalKind ¶
type PrincipalKind string
PrincipalKind is the kind of credential a client uses, from Client.WhoAmI.
const ( PrincipalUser PrincipalKind = "user" PrincipalAPIKey PrincipalKind = "api_key" PrincipalServiceAccount PrincipalKind = "service_account" PrincipalUnknown PrincipalKind = "unknown" )
type RetryOptions ¶
type RetryOptions struct {
// MaxRetries is the number of retries after the first attempt. Zero means
// the default, 2; a negative value disables retries.
MaxRetries int
// BaseDelay is the backoff before the first retry; it doubles each time,
// with jitter. Default 100ms.
BaseDelay time.Duration
// MaxDelay caps a single backoff. Default 2s. A rate-limited read
// (ResourceExhausted) is retried after the server's requested delay only
// when that delay is within MaxDelay; otherwise the error is returned.
MaxDelay time.Duration
}
RetryOptions configures retries of idempotent reads.
type Role ¶
type Role struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permissions []string `json:"permissions"`
}
Role is a named set of permissions. Roles are addressed by ID.
type WhoHasAccessOptions ¶
type WhoHasAccessOptions struct {
// Permission, when set, counts only grants that confer it, so a principal
// is listed exactly when a check of it passes through its own grants (or,
// for entries with ViaGroup, through that group).
Permission string
// ExpandGroups also lists every member of each listed group, once per
// group, with ViaGroup set.
ExpandGroups bool
}
WhoHasAccessOptions refines Client.WhoHasAccess.