AuthProfile

Summary: Like AuthUser, but designed to store login information in profile pages
Version: 8/25/2012
Prerequisites: EditAttributes
Status: Beta
Maintainer: Alex Eftimiades?
Users: (view? / edit)
Discussion: AuthProfile-Talk

Questions answered by this recipe

How can I store and organize the login information of lots of people in an efficient and scalable fashion?

All sections are optional, you can remove those that do not apply to your recipe, and add new ones.

Description

Like AuthUser, but designed to store login info on profile pages and group users by the group their profile page is in.

Usage

People's login information is stored on their profile page. The username and password get stored as page attributes. The group to which they belong corresponds to the group their profile page is in. To grant access to everyone in that group, simply set the password to @Group with 'Group' being the group you want to grant access to. For individuals, just list their username. To exclude an individual, use -username. To exclude a group, use -@Group. The same goes for HandleActions.

Groups that are named $SitePrefix.{some default password key} are recognized as @_site_{some default password key}. It is by default set to "Site". For example, if $SitePrefix=SitePre, then members of the group SitePreedit get all the privileges of the @_site_edit password. Similarly, the members of SitePreread get all the privileges of @_site_read. @_site_edit and the like will not work anymore--and no, you cannot set $SitePrefix to _site_. Don't ask, you just can't. However you can still use @nopass and @lock all the same. All in all, it seemed like a small price to pay for the extra versatility.

The hierarchy of who gets what privileges on who's pages is determined by $AuthProfileCascade. $AuthProfileCascade is an array that determines who has higher authority than who. For example, if I wanted to have "edit" be a higher authority than "read" I would set $AuthProfileCascade['edit']='read'. If I wanted to have "publish" be higher than "edit" and "read"--and have "edit" and "read" otherwise equal, I would do this: $AuthProfileCascade['edit']=array('read','edit'). You can of course define a hierarchy this way. You should not however define "admin." "admin" is exempt from this hierarchy and no matter what you do, you should not be able to keep admin from doing anything. This is not well tested yet, so I do not know for sure about any and all circumstances, but this is the idea, and I did my best to implement it in the code.

The site, by default, will give 'edit' and higher privileges to anyone one level higher than a SitePrefix group. For example, if my $AuthProfileCascade looks like this:

$AuthProfileCascade=array('attr'=>'publish','publish'=>array('edit','upload'),'edit'=>'read','read'=>'wikipublisher');

, and you are using the default $SitePrefix (and $GroupDelimiter), then Sitepublish/GroupAttributes should look something like this:

read: @Siteread
edit: @Siteattr
publish: @Siteattr
attr: @Siteattr

Notice how the privileges necessary to anything 'edit' or higher is set to the group one level higher (Siteattr) than the one you are looking at (Sitepublish). Siteedit will look like this:

read: @Siteread
edit: @Sitepublish
publish: @Sitepublish
attr: @Sitepublish

If you had in your $AuthProfileCascade=array('attr'=>'edit','publish'=>'edit'); then Siteedit's group attributes would look like this:

read: @Siteread
edit: @Siteattr, @Sitepublish
publish: @Siteattr, @Sitepublish
attr: @Siteattr, @Sitepublish

Note that these are all "set by site" passwords. You can always go in and manually change them, but this seemed like the best way of doing things out of the box. This allows people to create new users of lower authority than themselves. I thought that would generally be a desirable feature.

Originally, admins would belong (by default) to the Siteadmin group and can do anything they want no matter what rules you set. In practice, it is not a good idea to have more than one admin because they would be able to edit eachother's login information. So I decided to have a single admin:

$AuthProfileSalt='my super secret salt';
username:admin
password:set by DefaultPasswords['admin']=crypt('password', $AuthProfileSalt); 

Either way, the admin is separate from the hierarchy.

You must have at least 'attr' authorization to edit a page's username and password fields. Everyone has 'attr' authorization on their own profile pages.

When using this recipe, the other default password values do not really have any use anymore. I thought about making use of them as a default password for the groups, and may impelement that in the future, but decided against it for the present because it just did not seem to add anything.

Installation

$salt='my secret salt';
$DefaultPasswords['admin'] = crypt('password',$salt);
$DefaultPasswords['attr'] = true;
$DefaultPasswords['edit'] = true;
$DefaultPasswords['publish']=true;
$AuthProfileCascade=array('attr'=>'publish','publish'=>array('edit','upload'),'edit'=>'read','read'=>'wikipublisher');
include_once("$FarmD/cookbook/authprofile.php");

This recipe includes editattr, so you need to put the above lines in your config.php just before you would include editattr.php.

Add the following lines somewhere in your Site.EditForm (you may have to login as 'admin' now):

(:if auth attr:)
$[Associated Username]: (:input e_username:)
$[Associated Password]: (:input e_password:)
(:ifend:)

Configuration

You can always change the $salt variable to whatever you like as often as you want. $GroupDelimiter is by default set to @ (the thing that comes in front of groups in the attributes form). $SitePrefix is, as mentioned earlier, set to Site by default. $AuthProfileCascade is something you might want to customize, but example configuration should be fine for most people. You should set your admin password to something secure. You need to use a salt and set the $AuthProfileSalt variable. crypt() just does not seem to be consistent otherwise.

The default passwords are either set to true or false (except for admin of course). Default passwords are essentially treated as a list of authorizations to check for. It runs through all the DefaultPasswords and checks for authorization. If they are set to false, people authorized by default. For example, setting DefaultPasswords['read']=false; will cause your site to be viewable by anyone by default. Again, you can always go in and change the authorization levels of individual groups and pages to your liking, but these are just the defaults.

How it Works

For those of you curious or concerned about the nuts and bolts of this system, here is how it all works.

Login

When you log in, the username and password you entered is checked to see if it matches those of any pages on the wiki. If it does, you are logged in as you would be using AuthUser. The array $AuthList keeps track of which group that page belonged to, and $GroupDelimiter.$ThatGroup is added to the array.

Viewing a Page

When you try to view a page, a function called AuthProfile determines whether you are authorized to view the page. It essentially reads the page and returns either the page if you are authorized, "false" if you are not (or the page does not exist), or directs you to a login prompt if the argument $authprompt=true and you are otherwise not authorized to view the page.

The way it determines if you are authorized or not is by checking and setting the variable $page['=auth'], but it is not a simple process. It essentially checks authorizations set by the site (defaults), the page's group's GroupAttributes page, then the page's attributes itself. The authorizations it checks depends strictly on what $DefaultPasswords are set to a value. If I did not set $DefaultPasswords['read'] (and pmwiki.php or the like did not set it for me), this attribute would not be checked for authorization. The variable $page['=passwd'] is set to the page's password. If the page's password is not set, the GroupAttributes password takes precedence, and if that is not set, AuthProfile uses the site's default settings. The level at which the value was set (at the page group or site level) is recorded in $page['=source'], and the function UserIsAuthorized($page['=passwd']) returns a boolean value for $page['=auth']. Last, AuthCascade follows the AuthProfileCascade hierarchy to essentially determine "if you are authorized to do 'this' then you are authorized to do 'that'."

There are some things in between like making sure a user has attr authorization to their own profile page and making sure people of a $SitePrefix group have 'edit and higher' privileges for on $SitePrefixes of lower authority (that part comes into play while determining site default passwords), but I hope you get the jest of how this all works.

I will continue to be checking for security vulnerabilities and adding functionality as I see fit, and please shoot me an email at alexeftimiades@gmail.com if you have any questions or comments.

TODO

  • Keep looking for security vulnerabilities.
  • Continue testing!

Change log / Release notes

8/25/2012 - Bug fix--would not save login info. 8/23/2012 - Minor changes should make it run faster. 8/22/2012 - Potential security holes fixed. Makes sure that someone has attr authorization before they are able to change username and password attributes.
I am not sure whether this was necessary, but better safe than hacked. Also restructured the authentication checking. Should be more efficient now. 8/18/2012 - Major security holes fixed. Only one Admin. Hierarchy enforced via password protection of groups.
8/16/2012 - Major change of saving login information as page attributes rather than page text variables. Password is stored as plaintext.\\ 8/15/2012 - Minor changes for security
8/13/2012 - Debut
If the recipe has multiple releases, then release notes can be placed here. Note that it's often easier for people to work with "release dates" instead of "version numbers".

See also

Contributors

aeftimia?

Comments

Thanks to Peter Bowers for helping me learn the inner workings of PmWiki. See discussion at AuthProfile-Talk

User notes? : If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.