ImplementationReference
This is the implementation reference for the Cookbook.UserAuth2 recipe. It is valid for the 2.0 release version.
Permission table master index
#..., (comment; note that the comma in the end is necessary) @<group> (indicates the membership in and interprets the entailing rights of <group> as acquiring all of them; honoured at the place of occurrence) * (allow everything) rd_... (allowed to read ed_... edit up_... upload hi_... view history (page changes)) xx_... (joker level matching on any page related level) pr (allowed to change profiles) pw (allowed to change its own password) ps (allowed to set passwords of users below) ad (allowed access to admin tool, for changing user rights etc) cu (allowed to create new/ delete user/group) eu (allowed to edit user/group permissions) ip (allowed to change ip related login restrictions) rd_group.page (rd is placeholder for any page related level here) rd_group.* rd_group*.* rd_*.*a???b* (some complex page name pattern with usual wild cards ? and *) rd_*.* -rd_... (negation of the specified right) ...{$AuthId}... (in LoggendInUsers record, this is replaced by upper cased version of user name) -@... not allowed -* not allowed
Permission evaluation process
When calling the UserAuth() function, it is decided based on authprompt whether some page should be printed out (then go to TryAccessingPage()) or whether just a permission query takes place (then HasCurrentUserPerm()). In the latter case the following stages are encountered (in this order) to obtain the final permission result:
HasCurrentUserPerm
: Asking query cache for result if already existingHasCurrentUserPermUncached
: Silent granting right by ip addressCheckUserPerms
: Identity based hardwired permission checkingCheckUserPermsWithRecord
: Perm checking based on user-specific record, examining perm tables
The following table summarizes the spec for evaluating the permission result (P_c(p,l)
). The pair (p,l)
refers to the queried page and level, and c
to the connecting client.
The t(g,u)
is the permission table granted by granter g
to user u
.
Client permission | P_c(p,l) = Or_{ip ranges i} (matches(i, address(c)) and R_i(p,l)) or R_{GuestUsers}(p,l) or (authenticated(c) and (R_{LoggedInUsers}(p,l) or R_{username(c)}(p,l)) |
User permission | R_u(p,l) = Or_{granter g, with g is patron of u} (R_g(p,l) and S_{t(g,u)}(p,l)) |
Recursion seed | R_{admin}(p,l) = true, if (p,l) is valid |
Permission by Table | S_t(p,l) = { S_{t'}(p,l), if the last entry of t is not applicable true, if last entry is applicable and affirmative false, if last entry is applicable and denying S_{t'}(p,l) or R_m(p,l), if entry grants membership in group m } (here t' is the table t with the last entry removed, and S_{emptytable}(p,l)=false) |
Note that when $UA2AllowMultipleGranters
is false (default), you will always have only one granter (the parent), and thus the multiple Or
in R_u(p,l)
reduces to its argument. Note also that the granter must be a patron of the user concerned - arbitrary "granting around" is not allowed.
Public functions provided
function UserAuth($pagename
, $level, $authprompt=true) // for interfacing/compatibility function UserAuth2($pagename
, $level, $authprompt=true) function TryAccessingPage($pagename
, $level) function HasCurrentUserPerm($page, $level) function HasCurrentUserPermForAction($page, $action) // wrapper which employs$HandleAuth
to resolve action to level function CheckUserPerms($user, $page, $level, $groupaction = false) function CheckUserPermsForAction($user, $page, $action, $groupaction = false) // wrapper which employs$HandleAuth
to resolve action to level function mayCurrAdminActOnPermHolder($action, $admin_action, $tool_username, $groupaction = false)
Directory structure
userauth2.php userauth2/ userauth2-admintool.php userauth2-pwchange.php userperms/ groupperms/ ipranges/ profiles/
Data structures
Permission record
Array with keys:
description => parent => loginFromIpsOnly => (array of ip ranges; if undefined, then check disabled) perms => [granter1 => ..., granter2 => ..., ...]
User profile
Array with keys:
password => (in crypt()ed form) cookiekey => (stores the random key of the authentication cookie) cookiekeycreatetime => (... and that's expiration) email => (not yet implemented) notification => (not yet implemented)
Session data
Array with keys:
firststarttime administrative keys for security and expiration lastrevivaltime remote_addr site_identifier POST_data => (key1 => val1, ...) carried-over _POST data upon session expiration cachestarttime when the perm record and query cache was last purged username empty string when not logged in auth_message what appears on page when login fails editlocks => (filename1 => locktime, ...) what permission records the client currently edits profile currently not used iprangerecords => (iprange1 => record, ...) grouppermrecords => (group1 => record, ...) userpermrecords => (user1 => record, ...) permqueries => ("level page" => auth_result) page/level pairs with their respective permission result for the current user (with the current authentication status) target_url where to redirect in the follow-up of a forced login prev_contentpage previous content page browsed (= where to redirect after a self-inititaed login)
Markup generated
(:loginform:) (as before) (:if loggedin:) (as before) (:if memberOf <group>:) (untested) (:if member @<group>:) (for backward compatibility, untested) (:if ipMatches <range>:)
Functions/Variables of interest
Pmwiki variables manipulated
$AuthFunction w (set to "UserAuth")$AuthId
w$HandleAuth
a $HandleActions w
Pmwiki actions (re)defined
SDVA($HandleAuth, array( // action => level 'admin' => 'admin', 'pwchange' => 'pwchange', 'pwset' => 'pwset', 'profile' => 'profile', // not used at the moment 'diff' => 'history', 'edituserperms' => 'edituserperms', // just relevant UA2-internally 'createuserperms' => 'createuserperms', // 'setipperms' => 'setipperms' // ));
Global variables generated
$IsUserLoggedIn read-only by other modules $OnCreateUserFunc writable
Some configuration variables
(see code for complete list and detailed description)
SDV($UA2EnforceFixedClientIp, true); SDV($UA2SiteIdentifier, __FILE__); SDV($UA2SessionMaxInactivityTime, 2*60*60); // In seconds, default 2 hours. SDV($UA2SessionMaxLifeTime, 24*60*60); // In seconds, default 1 day. SDV($UA2AllowPostCompletion, true); SDV($UA2EnablePermCaching, true); // might be useful to set to false during debugging SDV($UA2MaxPermRecordCacheSize, 20); SDV($UA2MaxPermQueryCacheSize, 100); SDV($LoginPage, "Site.Login"); SDV($HomePage, "Main.HomePage"); SDV($UA2AfterSILoginRedirectTo, ''); // default '' = previous content page SDV($UA2AfterLogoutRedirectTo, $LoginPage); SDV($UA2AdminLoginFromIpsOnly, ''); // default '' = no restriction SDV($UA2AllowCookieLogin, false); SDV($UA2CookiePrefix, ''); SDV($UA2CookieExpireTime, 60*60*24*30); // cookie default expiration in seconds SDV($UA2LoginLockMsgFmt, ''); SDV($UA2AllowMultipleGranters, false); // this option is honoured only on the UI level SDV($UA2PermEditLockTimeout, 60*60*12);
PHP directives manipulated
ini_set('session.name', 'PHPSESSID' . strtoupper(md5($UA2SiteIdentifier))); ini_set('session.save_path', $UA2SessionSavePathDir); ini_set('session.cache_expire', max($UA2SessionMaxLifeTime/60, ini_get('session.cache_expire'))); ini_set('session.gc_maxlifetime', max($UA2SessionMaxLifeTime, ini_get('session.gc_maxlifetime')));
Valid strings
Everything case sensitive:
- User names: letters, digits, underscore; must begin with letter
- User group names: letters, digits, underscore; must begin with letter
- Page names: letters, digits
Rest
perm semantics: I. Hierarchy - every user and every group belongs to a direct (single) parent - a parent is always a proper user (not a group) - a granter must be always a proper user (not a group) - admin is root parent - as chaining is possible, a user can effectively have many (indirect) parents (called patrons) - the user may be granted rights from each of its patrons (independently); thus the users rights consist of the or-conjunction of all these rights - a patron can grant only rights it possess itself; this rule is enforced dynamically - a group membership is only honoured if the granter is patron of the group (#201) - a patron can view and alter the perms of all of its children (if "ad" right is granted), including the ones that are granted by intermediate patrons II. Special users There are special users, "admin", "GuestUser" (for unauthenticated users), "LoggedInUser" (for any user that is authenticated). For these hold: - all users are granted at least the GuestUser rights - all logged in users are granted at least LoggedInUser rights (these two points are only valid for proper users, i.e. not for groups; since we ultimately will check permissions always for proper users, the guest user or logged in user privileges will come into play anyway at the very beginning of traversing the perm hierachy; see #004) - "admin" can do everything - apart from 'admin' also the special users have parents and all the regular perm records, so can be assigned to be administered by non-'admin' users as well. - but: the guest and logged in user group can be created or deleted only by 'admin' III. Interpretation of perm table - default: deny all - interpretation starts from the top of the perm table, leaving all decls in order - if location specifier matches, then adjusts the current perm result accordingly - granted group memberships are honoured in their place of occurrence, but only in affirmative direction (as of 2.0-beta5) - note that the final result will always be masked by the permission of the direct parent IV. Hardwired rights: - the login page is readable by everyone [- the home page is readable by everyone (used for redirection)] (removed as of 2.0-beta4, #010) V. Policies on the alteration of permissions - the current admin can see only his children - the current admin can (see? and) change permissions only of his children - the current admin can see permissions granted by anyone to his children (this can entail only the other patrons of these children, since anyone else has no right (and no power) to grant permissions to the children) - the current admin can set the parent of the user only to himself or users which are his children (since a user has always less rights than his parent, this avoids extending permissions by switching to a higher parent) [parent switching could also be completely forbidden] VI. Specialities introduced by groups: - A granter may grant membership only to groups he is a patron of. (#201) - When interpreting the GuestUsers table for non-logged-in users, all admin tool related actions should be denied, since these would be bound to introduce inconsistencies in the perm system since a defined parent is usually needed for admin actions. To have this, the 'admin' action (only, for speed) is denied in CheckUserPermsWorker() (targetting UI issued queries), and the actual enforcement of ALL admin related actions is done in mayCurrentAdminActOnPermHolder() (we rely on that only this is used everywhere in the admin tool for permission checks, which is true). (#301) =================================== Valid characters etc. - user and group names are case sensitive - ... may contain only letters and digits (no underscores), must start with letter - reserved names are case insensitive versions of 'admin' and the guest user group and logged in user group - user and group names are implementationally separated (can be distinguished), but still on creation are forbidden to conflict =================================== - you cant login as group or ip range - groups (and ip ranges) have no profile - groups (and ip ranges) have no loginFromIpsOnly field set =================================== - on every page request, it is checked once (at the beginning) whether the permissions have been updated - auth cookie is deleted when user logs out or - not reachable - the "persistent" checkbox is disabled. - an admin edit session is started whenever the client enters the edit form of some user and finished when logging out. (or after saving or when pressing cancel in the form, still to implement) - a new empty profile is created (together with an empty perm record) whenever a new user is created; the inital password is the empty password =================================== - the variable $origuser is used to "feed through" the user name for which the outer-most (root) permission query was started, to the end of having it when replacing the {$AuthId} occurences in the LoggedInUsers perm record, #011 - make sure on creation of a user/group that it does not yet exist, both with upper- and lower cased first character; this assures that we can identify a valid page name with each user (which has to be ucfirst) without possibility of conflict; (needed for implementing {$AuthId} in perm tables); #012
Remarks on certain constructions
Permission cache
Permission evaluation results (perm queries) and loaded permission records are cached to speed up the evaluation process ("L1" and "L2" cache). Set $UA2EnablePermCaching
to false to disable it.
$UA2MaxPermQueryCacheSize
and $UA2MaxPermRecordCacheSize
control the respective sizes of the caches (20 and 100 by default). Using a time stamp, the perm cache gets invalidated every time someone changes something in the permission setup.
Edit locks
The edit locks prevent admins from editing one single permission record concurrently. Edit locks are released whenever (a) the admin logs out, (b) presses 'cancel' in the edit form, or (c) the edit lock just expires. By default, the edit locks expires after 12 hours; use $UA2PermEditLockTimeout
to control this.
UA2ChUsPmCallingStack
It can happen the when specifying group memberships, a circular dependence of perm records is created. To avoid infinite recursion, a calling stack is used which is filled up with users/groups examined so far. Upon occurence of a circle, the permission evaluation is bound and a negative result is locally returned.
Redirection
The redirection is accomplished using two variables, target_url
and prev_contentpage
, used for redirection after triggered or self-initiated login, respectively. In the first case, the client accessed a resource and was found to be not privileged and not authenticated. After login he is then directed to that resource, if privileged. In the second case, the client just happened to want to login himself, and is by default redirected to the last content page (browse level) after successful authentication, unless otherwise stated via $UA2AfterSILoginRedirectTo
option.
Groupaction
In the code there are many functions taking a "user" parameter as argument and have a parameter "groupaction" defaulting to false. The meaning is that if groupaction is true, the user argument is actually to be interpreted as group (a user group).