security

package
v1.19.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	RefusalReasonEmptyAttributeList       = "empty_attribute_list"
	RefusalReasonNoAttributeGranted       = "no_attribute_granted"
	RefusalReasonAllVotersAbstained       = "all_voters_abstained"
	RefusalReasonNoVoterSupportsAttribute = "no_voter_supports_attribute"
	RefusalReasonAffirmativeNoGrant       = "affirmative_no_grant"
	RefusalReasonConsensusDenied          = "consensus_denied"
	RefusalReasonConsensusTie             = "consensus_tie"
	RefusalReasonUnanimousDenied          = "unanimous_denied"
	RefusalReasonUnanimousNoGrant         = "unanimous_no_grant"
)

The refusal reasons name which branch produced a 403. Every branch answers the same status and the same client-facing message, so without a reason the journal could not tell a real denial from a firewall whose attribute no configured voter looks at — a wiring fault answered fail-closed. The access control listener reads these to pick the level it files the refusal at.

View Source
const (
	KernelFirewallListenerPriority = 50

	KernelAccessControlListenerPriority = 20
)
View Source
const (
	ServiceFirewallManager = "service.security.firewall_manager"
)

Variables

This section is empty.

Functions

func FirewallManagerFromContainer

func FirewallManagerFromContainer(serviceContainer containercontract.Container) securitycontract.FirewallManager

func FirewallManagerMustFromContainer

func FirewallManagerMustFromContainer(serviceContainer containercontract.Container) securitycontract.FirewallManager

func IsGranted

func IsGranted(runtimeInstance runtimecontract.Runtime, role string) bool

func RegisterKernelAccessControlListener

func RegisterKernelAccessControlListener(kernelInstance kernelcontract.Kernel, registry *FirewallRegistry)

func RegisterKernelSecurityResolutionListener

func RegisterKernelSecurityResolutionListener(kernelInstance kernelcontract.Kernel, registry *FirewallRegistry)

func SecurityContextSetOnRuntime

func SecurityContextSetOnRuntime(runtimeInstance runtimecontract.Runtime, securityContext *SecurityContext)

the runtime and its scope are read through the interface, the way every other door that takes a substitutable runtime reads them: Runtime is an interface application code may implement, so a typed nil passes a plain comparison and the method call below reaches a nil receiver. runtime.New refuses such a scope at construction, which leaves these two doors as the entries where one can still arrive.

Types

type AccessControl

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

func NewAccessControl

func NewAccessControl(rules ...AccessControlRule) *AccessControl

func (*AccessControl) Match

func (instance *AccessControl) Match(path string) ([]string, bool)

Match resolves by category before position: an exact rule beats every prefix rule, a longer prefix beats a shorter one regardless of registration order, every prefix beats every regex, and the empty-prefix fallback answers only when nothing else did. Position in the rule list — what the merge strategies order — breaks only the ties inside a category: equal-length prefixes, regexes, exact duplicates and fallbacks each resolve to the first registered.

func (*AccessControl) Rules

func (instance *AccessControl) Rules() []AccessControlRule

type AccessControlRule

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

func NewAccessControlExactRule

func NewAccessControlExactRule(path string, attributes ...string) AccessControlRule

func NewAccessControlRawPrefixRule added in v1.19.0

func NewAccessControlRawPrefixRule(pathPrefix string, attributes ...string) AccessControlRule

NewAccessControlRawPrefixRule builds a rule that matches across segment boundaries: pathPrefix matches every path that begins with it, so "/admin" governs "/administrator" and "/admin-tools" as readily as "/admin/panel". It is the sharp tool, kept behind an explicit name because, being the longest match, a raw rule shadows a correctly bounded rule that would have denied — which is why PUBLIC_ACCESS is refused on it: a raw public rule opens every path that merely begins with the prefix. Reach for NewAccessControlRule unless a cross-segment reach is exactly what the rule means.

func NewAccessControlRegexRule

func NewAccessControlRegexRule(pattern string, attributes ...string) AccessControlRule
NewAccessControlRegexRule builds a rule that matches when the pattern is found anywhere in the

canonicalized request path. The pattern is compiled UNANCHORED and tested with regexp.MatchString, so it is a substring match, not a whole-path one: "/public" matches "/admin/public-notes" and "/x/publications" as readily as "/public". This is deliberate and mirrors the path regex of other frameworks, but it is the opposite of a route requirement, which melody anchors with ^(?:…)$ — so a rule meant to name one section must anchor itself. Write "^/public(/|$)" to bound it to the /public tree. Regex rules are the lowest match priority (after exact and prefix rules), and among themselves the first registered that matches wins.

func NewAccessControlRule

func NewAccessControlRule(pathPrefix string, attributes ...string) AccessControlRule

NewAccessControlRule builds a rule bounded to a path SEGMENT: pathPrefix matches the path itself and any descendant under a "/" boundary, never a path that merely begins with the same letters — "/admin" governs "/admin" and "/admin/panel" but not "/administrator". This is the rule a caller reaches for by default, so the plain name is the bounded one; a rule that must reach across segment boundaries is the explicit exception, NewAccessControlRawPrefixRule. An empty prefix is refused rather than made a catch-all, and PUBLIC_ACCESS is allowed here because a segment-bounded public rule cannot shadow a bounded denial the way a raw one can.

func NewAccessControlRuleWithSegmentPrefix deprecated

func NewAccessControlRuleWithSegmentPrefix(pathPrefix string, attributes ...string) AccessControlRule

Deprecated: use NewAccessControlRule, which now builds the segment-prefix rule this constructor always built. Kept as an alias so existing callers continue to compile.

type AccessDecisionManager

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

func NewAccessDecisionManagerWithVoters added in v1.7.1

func NewAccessDecisionManagerWithVoters(strategy securitycontract.DecisionStrategy, voters []securitycontract.Voter) *AccessDecisionManager

func (*AccessDecisionManager) DecideAll

func (instance *AccessDecisionManager) DecideAll(token securitycontract.Token, attributes []string, subject any) error

func (*AccessDecisionManager) DecideAny

func (instance *AccessDecisionManager) DecideAny(token securitycontract.Token, attributes []string, subject any) error

func (*AccessDecisionManager) Strategy added in v1.7.1

func (*AccessDecisionManager) Voters added in v1.7.1

func (instance *AccessDecisionManager) Voters() []securitycontract.Voter

func (*AccessDecisionManager) WithRoleHierarchy added in v1.19.0

func (instance *AccessDecisionManager) WithRoleHierarchy(roleHierarchy *RoleHierarchy) securitycontract.AccessDecisionManager

WithRoleHierarchy answers a manager whose built-in role voters read the expanded roles, leaving every other voter as it was: melody knows what a RoleVoter does with a role and cannot know what a foreign voter would do with an expanded set, so wrapping one would be a decision taken on the integrator's behalf. An integrator who does want it wraps the voter with NewRoleHierarchyVoter, which takes any Voter. A nil hierarchy answers the manager unchanged, so the caller need not branch.

type AnonymousToken

type AnonymousToken struct {
}

func NewAnonymousToken

func NewAnonymousToken() *AnonymousToken

func (*AnonymousToken) IsAuthenticated

func (instance *AnonymousToken) IsAuthenticated() bool

func (*AnonymousToken) Roles

func (instance *AnonymousToken) Roles() []string

func (*AnonymousToken) UserIdentifier

func (instance *AnonymousToken) UserIdentifier() string

type ApiKeyHeaderAuthenticator

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

func NewApiKeyHeaderAuthenticator

func NewApiKeyHeaderAuthenticator(headerName string, expectedValue string, userId string, roles []string) *ApiKeyHeaderAuthenticator

func (*ApiKeyHeaderAuthenticator) Authenticate

func (instance *ApiKeyHeaderAuthenticator) Authenticate(request httpcontract.Request) (securitycontract.Token, error)

func (*ApiKeyHeaderAuthenticator) Supports

func (instance *ApiKeyHeaderAuthenticator) Supports(request httpcontract.Request) bool

type ApiKeyHeaderRule

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

func NewApiKeyHeaderRule

func NewApiKeyHeaderRule(matcher securitycontract.Matcher, headerName string, expectedValue string) *ApiKeyHeaderRule

func (*ApiKeyHeaderRule) Applies

func (instance *ApiKeyHeaderRule) Applies(request httpcontract.Request) bool

func (*ApiKeyHeaderRule) Check

func (instance *ApiKeyHeaderRule) Check(request httpcontract.Request) error

type AuthenticatedToken

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

func NewAuthenticatedToken

func NewAuthenticatedToken(userIdentifier string, roles []string) *AuthenticatedToken

func (*AuthenticatedToken) IsAuthenticated

func (instance *AuthenticatedToken) IsAuthenticated() bool

func (*AuthenticatedToken) Roles

func (instance *AuthenticatedToken) Roles() []string

func (*AuthenticatedToken) UserIdentifier

func (instance *AuthenticatedToken) UserIdentifier() string

func (*AuthenticatedToken) WithRoles added in v1.19.0

func (instance *AuthenticatedToken) WithRoles(roles []string) securitycontract.Token

WithRoles answers this token's own twin under another role set, which is what the role hierarchy voter asks of the token it is about to expand rather than rebuilding one of its own. The receiver is not modified: the voter holds the caller's token and hands the twin to a delegate that may keep it.

type AuthenticatorManager

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

func NewAuthenticatorManager

func NewAuthenticatorManager(authenticators ...securitycontract.Authenticator) *AuthenticatorManager

func (*AuthenticatorManager) Authenticate

func (instance *AuthenticatorManager) Authenticate(request httpcontract.Request) (securitycontract.Token, bool, error)

type AuthenticatorTokenSource

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

func NewAuthenticatorTokenSource

func NewAuthenticatorTokenSource(manager *AuthenticatorManager) *AuthenticatorTokenSource

func (*AuthenticatorTokenSource) Name

func (instance *AuthenticatorTokenSource) Name() string

func (*AuthenticatorTokenSource) Resolve

func (instance *AuthenticatorTokenSource) Resolve(runtimeInstance runtimecontract.Runtime, request httpcontract.Request) (securitycontract.Token, error)

type AuthorizationDeniedEvent

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

func NewAuthorizationDeniedEvent

func NewAuthorizationDeniedEvent(request httpcontract.Request, attributes []string, err error) *AuthorizationDeniedEvent

func (*AuthorizationDeniedEvent) Attributes

func (instance *AuthorizationDeniedEvent) Attributes() []string

func (*AuthorizationDeniedEvent) Err

func (instance *AuthorizationDeniedEvent) Err() error

func (*AuthorizationDeniedEvent) Request

func (instance *AuthorizationDeniedEvent) Request() httpcontract.Request

type AuthorizationGrantedEvent

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

func NewAuthorizationGrantedEvent

func NewAuthorizationGrantedEvent(request httpcontract.Request, attributes []string) *AuthorizationGrantedEvent

func (*AuthorizationGrantedEvent) Attributes

func (instance *AuthorizationGrantedEvent) Attributes() []string

func (*AuthorizationGrantedEvent) Request

func (instance *AuthorizationGrantedEvent) Request() httpcontract.Request

type CompiledConfiguration

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

func NewCompiledConfiguration

func NewCompiledConfiguration(firewalls []*CompiledFirewall, globalAccessControl *AccessControl) *CompiledConfiguration

func (*CompiledConfiguration) Firewalls

func (instance *CompiledConfiguration) Firewalls() []*CompiledFirewall

func (*CompiledConfiguration) GlobalAccessControl

func (instance *CompiledConfiguration) GlobalAccessControl() *AccessControl

type CompiledFirewall

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

func NewCompiledFirewall

func NewCompiledFirewall(
	name string,
	matcher securitycontract.Matcher,
	matcherDescription string,
	rules []securitycontract.Rule,
	tokenSource securitycontract.TokenSource,
	accessControl *AccessControl,
	accessDecisionManager securitycontract.AccessDecisionManager,
	roleHierarchy *RoleHierarchy,
	entryPoint securitycontract.EntryPoint,
	accessDeniedHandler securitycontract.AccessDeniedHandler,
	loginPath string,
	logoutPath string,
	loginHandler securitycontract.LoginHandler,
	logoutHandler securitycontract.LogoutHandler,
	roleHierarchySource Source,
	accessDecisionManagerSource Source,
	accessControlSource Source,
	entryPointSource Source,
	accessDeniedHandlerSource Source,
) *CompiledFirewall

func (*CompiledFirewall) AccessControl

func (instance *CompiledFirewall) AccessControl() *AccessControl

func (*CompiledFirewall) AccessDecisionManager

func (instance *CompiledFirewall) AccessDecisionManager() securitycontract.AccessDecisionManager

func (*CompiledFirewall) AccessDeniedHandler

func (instance *CompiledFirewall) AccessDeniedHandler() securitycontract.AccessDeniedHandler

func (*CompiledFirewall) EntryPoint

func (instance *CompiledFirewall) EntryPoint() securitycontract.EntryPoint

func (*CompiledFirewall) Login

func (*CompiledFirewall) LoginPath

func (instance *CompiledFirewall) LoginPath() string

func (*CompiledFirewall) Logout

func (*CompiledFirewall) LogoutPath

func (instance *CompiledFirewall) LogoutPath() string

func (*CompiledFirewall) Matcher

func (instance *CompiledFirewall) Matcher() securitycontract.Matcher

func (*CompiledFirewall) MatcherDescription

func (instance *CompiledFirewall) MatcherDescription() string

func (*CompiledFirewall) Name

func (instance *CompiledFirewall) Name() string

func (*CompiledFirewall) RoleHierarchy

func (instance *CompiledFirewall) RoleHierarchy() *RoleHierarchy

func (*CompiledFirewall) Rules

func (instance *CompiledFirewall) Rules() []securitycontract.Rule

func (*CompiledFirewall) Sources

func (instance *CompiledFirewall) Sources() (Source, Source, Source, Source, Source)

func (*CompiledFirewall) TokenSource

func (instance *CompiledFirewall) TokenSource() securitycontract.TokenSource

type Firewall

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

func NewFirewall

func NewFirewall(rules ...securitycontract.Rule) *Firewall

func (*Firewall) Check

func (instance *Firewall) Check(request httpcontract.Request) error

type FirewallManager

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

func NewFirewallManager

func NewFirewallManager(compiledConfiguration *CompiledConfiguration) *FirewallManager

func (*FirewallManager) Firewall

func (instance *FirewallManager) Firewall(name string) (securitycontract.Firewall, error)

type FirewallRegistry

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

func NewFirewallRegistry

func NewFirewallRegistry(compiledConfiguration *CompiledConfiguration) *FirewallRegistry

func (*FirewallRegistry) GlobalAccessControl

func (instance *FirewallRegistry) GlobalAccessControl() *AccessControl

func (*FirewallRegistry) Match

func (instance *FirewallRegistry) Match(request httpcontract.Request) (*CompiledFirewall, bool)

type LoginFailureEvent

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

func NewLoginFailureEvent

func NewLoginFailureEvent(
	request httpcontract.Request,
	err error,
) *LoginFailureEvent

func (*LoginFailureEvent) Error

func (instance *LoginFailureEvent) Error() error

func (*LoginFailureEvent) Request

func (instance *LoginFailureEvent) Request() httpcontract.Request

type LoginSuccessEvent

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

func NewLoginSuccessEvent

func NewLoginSuccessEvent(
	request httpcontract.Request,
	token securitycontract.Token,
) *LoginSuccessEvent

func (*LoginSuccessEvent) Request

func (instance *LoginSuccessEvent) Request() httpcontract.Request

func (*LoginSuccessEvent) Token

func (instance *LoginSuccessEvent) Token() securitycontract.Token

type LogoutFailureEvent

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

func NewLogoutFailureEvent

func NewLogoutFailureEvent(request httpcontract.Request, err error) *LogoutFailureEvent

func (*LogoutFailureEvent) Error

func (instance *LogoutFailureEvent) Error() error

func (*LogoutFailureEvent) Request

func (instance *LogoutFailureEvent) Request() httpcontract.Request

type LogoutSuccessEvent

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

func NewLogoutSuccessEvent

func NewLogoutSuccessEvent(request httpcontract.Request) *LogoutSuccessEvent

func (*LogoutSuccessEvent) Request

func (instance *LogoutSuccessEvent) Request() httpcontract.Request

type MatchedAccessControlRule

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

func NewMatchedAccessControlRule

func NewMatchedAccessControlRule(
	pathPrefix string,
	attributes []string,
	source Source,
	ruleIndex int,
	firewall string,
) *MatchedAccessControlRule

func (*MatchedAccessControlRule) Attributes

func (instance *MatchedAccessControlRule) Attributes() []string

func (*MatchedAccessControlRule) Firewall

func (instance *MatchedAccessControlRule) Firewall() string

func (*MatchedAccessControlRule) PathPrefix

func (instance *MatchedAccessControlRule) PathPrefix() string

func (*MatchedAccessControlRule) RuleIndex

func (instance *MatchedAccessControlRule) RuleIndex() int

func (*MatchedAccessControlRule) Source

func (instance *MatchedAccessControlRule) Source() Source

type PathPrefixMatcher

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

func NewPathPrefixMatcher

func NewPathPrefixMatcher(prefix string) *PathPrefixMatcher

func (*PathPrefixMatcher) Matches

func (instance *PathPrefixMatcher) Matches(request httpcontract.Request) bool

type ResolverTokenSource

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

func NewResolverTokenSource

func NewResolverTokenSource(resolver securitycontract.TokenResolver) *ResolverTokenSource

func (*ResolverTokenSource) Name

func (instance *ResolverTokenSource) Name() string

func (*ResolverTokenSource) Resolve

func (instance *ResolverTokenSource) Resolve(runtimeInstance runtimecontract.Runtime, request httpcontract.Request) (securitycontract.Token, error)

type RoleHierarchy

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

func NewRoleHierarchy

func NewRoleHierarchy(inheritedRolesByRole map[string][]string) *RoleHierarchy

func (*RoleHierarchy) ExpandRoles

func (instance *RoleHierarchy) ExpandRoles(roles []string) []string

type RoleHierarchyAware added in v1.19.0

type RoleHierarchyAware interface {
	WithRoleHierarchy(roleHierarchy *RoleHierarchy) securitycontract.AccessDecisionManager
}

RoleHierarchyAware is the optional capability an AccessDecisionManager implements to receive the declared role hierarchy at compilation, answering the manager that applies it.

The compilation asks for it and nothing else: it used to assert on the concrete *AccessDecisionManager, so a manager of the integrator's own — even a wrapper that only delegated, to log or cache decisions — skipped the whole hierarchy upgrade. ROLE_ADMIN: [ROLE_USER] then had no effect on the enforcement path while security.IsGranted, which expands the hierarchy straight from the compiled firewall, kept answering true for the same request: one door granted and the other answered 403, with no record on either.

A manager that does not implement it and is handed a hierarchy is refused at compilation by name, because the alternative is that silence.

type RoleHierarchyVoter

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

func NewRoleHierarchyVoter

func NewRoleHierarchyVoter(roleHierarchy *RoleHierarchy, delegate securitycontract.Voter) *RoleHierarchyVoter

NewRoleHierarchyVoter takes any Voter as its delegate, not only the built-in RoleVoter: the wrapper calls nothing but Supports and Vote, so the narrower parameter bought nothing and cost an integrator's own voter — multi-tenant, ownership — the ability to see the expanded roles at all. The only way out was copying this file, which meant every foreign voter reimplementing the expansion rule.

func (*RoleHierarchyVoter) Supports

func (instance *RoleHierarchyVoter) Supports(attribute string, subject any) bool

func (*RoleHierarchyVoter) Vote

func (instance *RoleHierarchyVoter) Vote(token securitycontract.Token, attribute string, subject any) securitycontract.VoteResult

type RoleVoter

type RoleVoter struct {
}

func NewRoleVoter

func NewRoleVoter() *RoleVoter

func (*RoleVoter) Supports

func (instance *RoleVoter) Supports(attribute string, subject any) bool

func (*RoleVoter) Vote

func (instance *RoleVoter) Vote(token securitycontract.Token, attribute string, subject any) securitycontract.VoteResult

type RolesReplacer added in v1.19.0

type RolesReplacer interface {
	WithRoles(roles []string) securitycontract.Token
}
RolesReplacer is the optional capability a token implements to answer its own twin under a different role set. It exists because the delegate of a hierarchy voter is any Voter, and a voter of the application's own reads more than Roles(): it asserts the concrete token, or an interface of its own on it, to learn WHICH tenant or which owner the request speaks for. A token rebuilt as one of melody's own answers that assertion with a type the voter has never seen, so the voter abstains — and a voter that would have REFUSED, abstaining under the affirmative strategy beside a role voter that grants, hands out the access it exists to withhold. A token that answers its own twin keeps its dynamic type through the expansion and the delegate is none the wiser.

The capability is optional because Token is a published contract of a stable major and cannot grow a method. A token that does not carry it is rebuilt as before, which is what it already got.

type SecurityContext

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

func NewSecurityContext

func NewSecurityContext(
	firewall *CompiledFirewall,
	token securitycontract.Token,
) *SecurityContext

func SecurityContextFromRuntime

func SecurityContextFromRuntime(runtimeInstance runtimecontract.Runtime) (*SecurityContext, bool)

func (*SecurityContext) AccessControlSource

func (instance *SecurityContext) AccessControlSource() Source

func (*SecurityContext) AccessDecisionManagerSource

func (instance *SecurityContext) AccessDecisionManagerSource() Source

func (*SecurityContext) AccessDeniedHandlerSource

func (instance *SecurityContext) AccessDeniedHandlerSource() Source

func (*SecurityContext) EntryPointSource

func (instance *SecurityContext) EntryPointSource() Source

func (*SecurityContext) Firewall

func (instance *SecurityContext) Firewall() *CompiledFirewall

func (*SecurityContext) IsGranted

func (instance *SecurityContext) IsGranted(role string) bool

func (*SecurityContext) MatchedFirewallMatcher

func (instance *SecurityContext) MatchedFirewallMatcher() string

func (*SecurityContext) MatchedRule

func (instance *SecurityContext) MatchedRule() *MatchedAccessControlRule

func (*SecurityContext) RoleHierarchySource

func (instance *SecurityContext) RoleHierarchySource() Source

func (*SecurityContext) SetMatchedRule

func (instance *SecurityContext) SetMatchedRule(matchedRule *MatchedAccessControlRule)

func (*SecurityContext) Token

func (instance *SecurityContext) Token() securitycontract.Token

type Source

type Source string
const (
	SourceNone     Source = "none"
	SourceGlobal   Source = "global"
	SourceFirewall Source = "firewall"
	SourceMerged   Source = "merged"
)

type Token

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

func NewToken

func NewToken(user securitycontract.Token) *Token

func (*Token) IsAuthenticated

func (instance *Token) IsAuthenticated() bool

func (*Token) Roles

func (instance *Token) Roles() []string

func (*Token) User

func (instance *Token) User() securitycontract.Token

func (*Token) UserIdentifier

func (instance *Token) UserIdentifier() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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