Added a User_username table that links the external username with a StatusNet user_id

Added EmailAuthenticationPlugin
Added ReverseUsernameAuthenticationPlugin
Changed the StartChangePassword and EndChangePassword events to take a user, instead of a nickname
User::allowed_nickname was declared non-static, but used as if it was static, so I made the declaration static
This commit is contained in:
Craig Andrews 2009-11-12 20:12:00 -05:00
parent cefbad0159
commit ed690615de
14 changed files with 358 additions and 104 deletions

View File

@ -528,12 +528,12 @@ EndCheckPassword: After checking a username/password pair
- $authenticatedUser: User object if credentials match a user, else null. - $authenticatedUser: User object if credentials match a user, else null.
StartChangePassword: Before changing a password StartChangePassword: Before changing a password
- $nickname: user's nickname - $user: user
- $oldpassword: the user's old password - $oldpassword: the user's old password
- $newpassword: the desired new password - $newpassword: the desired new password
EndChangePassword: After changing a password EndChangePassword: After changing a password
- $nickname: user's nickname - $user: user
UserDeleteRelated: Specify additional tables to delete entries from when deleting users UserDeleteRelated: Specify additional tables to delete entries from when deleting users
- $user: User object - $user: User object

View File

@ -170,7 +170,7 @@ class PasswordsettingsAction extends AccountSettingsAction
} }
$success = false; $success = false;
if(! Event::handle('StartChangePassword', array($user->nickname, $oldpassword, $newpassword))){ if(! Event::handle('StartChangePassword', array($user, $oldpassword, $newpassword))){
//no handler changed the password, so change the password internally //no handler changed the password, so change the password internally
$original = clone($user); $original = clone($user);
@ -186,7 +186,7 @@ class PasswordsettingsAction extends AccountSettingsAction
$this->serverError(_('Can\'t save new password.')); $this->serverError(_('Can\'t save new password.'));
return; return;
} }
Event::handle('EndChangePassword', array($nickname)); Event::handle('EndChangePassword', array($user));
} }
$this->showForm(_('Password saved.'), true); $this->showForm(_('Password saved.'), true);

View File

@ -114,7 +114,7 @@ class User extends Memcached_DataObject
return $result; return $result;
} }
function allowed_nickname($nickname) static function allowed_nickname($nickname)
{ {
// XXX: should already be validated for size, content, etc. // XXX: should already be validated for size, content, etc.
$blacklist = common_config('nickname', 'blacklist'); $blacklist = common_config('nickname', 'blacklist');
@ -190,7 +190,17 @@ class User extends Memcached_DataObject
$profile->query('BEGIN'); $profile->query('BEGIN');
if(!empty($email))
{
$email = common_canonical_email($email);
}
$nickname = common_canonical_nickname($nickname);
$profile->nickname = $nickname; $profile->nickname = $nickname;
if(! User::allowed_nickname($nickname)){
common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->nickname),
__FILE__);
}
$profile->profileurl = common_profile_url($nickname); $profile->profileurl = common_profile_url($nickname);
if (!empty($fullname)) { if (!empty($fullname)) {
@ -242,6 +252,10 @@ class User extends Memcached_DataObject
} }
} }
if(isset($email_confirmed) && $email_confirmed) {
$user->email = $email;
}
// This flag is ignored but still set to 1 // This flag is ignored but still set to 1
$user->inboxed = 1; $user->inboxed = 1;

View File

@ -566,3 +566,13 @@ modified = 384
user_id = K user_id = K
token = K token = K
[user_username]
user_id = 129
provider_name = 130
username = 130
created = 142
modified = 384
[user_username__keys]
provider_name = K
username = K

View File

@ -68,7 +68,6 @@ function getPath($req)
*/ */
function handleError($error) function handleError($error)
{ {
//error_log(print_r($error,1));
if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) {
return; return;
} }

View File

@ -1058,6 +1058,7 @@ function common_log($priority, $msg, $filename=null)
} }
} else { } else {
common_ensure_syslog(); common_ensure_syslog();
error_log($msg);
syslog($priority, $msg); syslog($priority, $msg);
} }
} }

View File

@ -48,20 +48,20 @@ abstract class AuthenticationPlugin extends Plugin
//should accounts be automatically created after a successful login attempt? //should accounts be automatically created after a successful login attempt?
public $autoregistration = false; public $autoregistration = false;
//can the user change their email address
public $email_changeable=true;
//can the user change their email address //can the user change their email address
public $password_changeable=true; public $password_changeable=true;
//unique name for this authentication provider
public $provider_name;
//------------Auth plugin should implement some (or all) of these methods------------\\ //------------Auth plugin should implement some (or all) of these methods------------\\
/** /**
* Check if a nickname/password combination is valid * Check if a nickname/password combination is valid
* @param nickname * @param username
* @param password * @param password
* @return boolean true if the credentials are valid, false if they are invalid. * @return boolean true if the credentials are valid, false if they are invalid.
*/ */
function checkPassword($nickname, $password) function checkPassword($username, $password)
{ {
return false; return false;
} }
@ -69,88 +69,116 @@ abstract class AuthenticationPlugin extends Plugin
/** /**
* Automatically register a user when they attempt to login with valid credentials. * Automatically register a user when they attempt to login with valid credentials.
* User::register($data) is a very useful method for this implementation * User::register($data) is a very useful method for this implementation
* @param nickname * @param username
* @return boolean true if the user was created, false if autoregistration is not allowed, null if this plugin is not responsible for this nickname * @return boolean true if the user was created, false if not
*/ */
function autoRegister($nickname) function autoRegister($username)
{ {
return null; $registration_data = array();
$registration_data['nickname'] = $username ;
return User::register($registration_data);
} }
/** /**
* Change a user's password * Change a user's password
* The old password has been verified to be valid by this plugin before this call is made * The old password has been verified to be valid by this plugin before this call is made
* @param nickname * @param username
* @param oldpassword * @param oldpassword
* @param newpassword * @param newpassword
* @return boolean true if the password was changed, false if password changing failed for some reason, null if this plugin is not responsible for this nickname * @return boolean true if the password was changed, false if password changing failed for some reason
*/ */
function changePassword($nickname,$oldpassword,$newpassword) function changePassword($username,$oldpassword,$newpassword)
{ {
return null; return false;
}
/**
* Can a user change this field in his own profile?
* @param nickname
* @param field
* @return boolean true if the field can be changed, false if not allowed to change it, null if this plugin is not responsible for this nickname
*/
function canUserChangeField($nickname, $field)
{
return null;
} }
//------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\ //------------Below are the methods that connect StatusNet to the implementing Auth plugin------------\\
function __construct() function onInitializePlugin(){
{ if(!isset($this->provider_name)){
parent::__construct(); throw new Exception("must specify a provider_name for this authentication provider");
}
function onStartCheckPassword($nickname, $password, &$authenticatedUser){
if($this->password_changeable){
$authenticated = $this->checkPassword($nickname, $password);
if($authenticated){
$authenticatedUser = User::staticGet('nickname', $nickname);
if(!$authenticatedUser && $this->autoregistration){
if($this->autoregister($nickname)){
$authenticatedUser = User::staticGet('nickname', $nickname);
}
}
return false;
}else{
if($this->authoritative){
return false;
}
}
//we're not authoritative, so let other handlers try
}else{
if($this->authoritative){
//since we're authoritative, no other plugin could do this
throw new Exception(_('Password changing is not allowed'));
}
} }
} }
function onStartChangePassword($nickname,$oldpassword,$newpassword) function onStartCheckPassword($nickname, $password, &$authenticatedUser){
{ //map the nickname to a username
if($this->password_changeable){ $user_username = new User_username();
$authenticated = $this->checkPassword($nickname, $oldpassword); $user_username->username=$nickname;
$user_username->provider_name=$this->provider_name;
if($user_username->find() && $user_username->fetch()){
$username = $user_username->username;
$authenticated = $this->checkPassword($username, $password);
if($authenticated){ if($authenticated){
$result = $this->changePassword($nickname,$oldpassword,$newpassword); $authenticatedUser = User::staticGet('id', $user_username->user_id);
if($result){ return false;
//stop handling of other handlers, because what was requested was done }
return false; }else{
}else{ $user = User::staticGet('nickname', $nickname);
throw new Exception(_('Password changing failed')); if($user){
//make sure a different provider isn't handling this nickname
$user_username = new User_username();
$user_username->username=$nickname;
if(!$user_username->find()){
//no other provider claims this username, so it's safe for us to handle it
$authenticated = $this->checkPassword($nickname, $password);
if($authenticated){
$authenticatedUser = User::staticGet('nickname', $nickname);
$user_username = new User_username();
$user_username->user_id = $authenticatedUser->id;
$user_username->provider_name = $this->provider_name;
$user_username->username = $nickname;
$user_username->created = DB_DataObject_Cast::dateTime();
$user_username->insert();
return false;
}
} }
}else{ }else{
if($this->authoritative){ if($this->autoregistration){
//since we're authoritative, no other plugin could do this $authenticated = $this->checkPassword($nickname, $password);
throw new Exception(_('Password changing failed')); if($authenticated && $this->autoregister($nickname)){
$authenticatedUser = User::staticGet('nickname', $nickname);
$user_username = new User_username();
$user_username->user_id = $authenticatedUser->id;
$user_username->provider_name = $this->provider_name;
$user_username->username = $nickname;
$user_username->created = DB_DataObject_Cast::dateTime();
$user_username->insert();
return false;
}
}
}
}
if($this->authoritative){
return false;
}else{
//we're not authoritative, so let other handlers try
return;
}
}
function onStartChangePassword($user,$oldpassword,$newpassword)
{
if($this->password_changeable){
$user_username = new User_username();
$user_username->user_id=$user->id;
$user_username->provider_name=$this->provider_name;
if($user_username->find() && $user_username->fetch()){
$authenticated = $this->checkPassword($user_username->username, $oldpassword);
if($authenticated){
$result = $this->changePassword($user_username->username,$oldpassword,$newpassword);
if($result){
//stop handling of other handlers, because what was requested was done
return false;
}else{
throw new Exception(_('Password changing failed'));
}
}else{ }else{
//let another handler try if($this->authoritative){
return null; //since we're authoritative, no other plugin could do this
throw new Exception(_('Password changing failed'));
}else{
//let another handler try
return null;
}
} }
} }
}else{ }else{
@ -164,9 +192,42 @@ abstract class AuthenticationPlugin extends Plugin
function onStartAccountSettingsPasswordMenuItem($widget) function onStartAccountSettingsPasswordMenuItem($widget)
{ {
if($this->authoritative && !$this->password_changeable){ if($this->authoritative && !$this->password_changeable){
//since we're authoritative, no other plugin could change passwords, so do render the menu item //since we're authoritative, no other plugin could change passwords, so do not render the menu item
return false; return false;
} }
} }
function onAutoload($cls)
{
switch ($cls)
{
case 'User_username':
require_once(INSTALLDIR.'/plugins/Authentication/User_username.php');
return false;
default:
return true;
}
}
function onCheckSchema() {
$schema = Schema::get();
$schema->ensureTable('user_username',
array(new ColumnDef('provider_name', 'varchar',
'255', false, 'PRI'),
new ColumnDef('username', 'varchar',
'255', false, 'PRI'),
new ColumnDef('user_id', 'integer',
null, false),
new ColumnDef('created', 'datetime',
null, false),
new ColumnDef('modified', 'timestamp')));
return true;
}
function onUserDeleteRelated($user, &$tables)
{
$tables[] = 'User_username';
return true;
}
} }

View File

@ -0,0 +1,25 @@
<?php
/**
* Table Definition for user_username
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class User_username extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'user_username'; // table name
public $user_id; // int(4) not_null
public $provider_name; // varchar(255) primary_key not_null
public $username; // varchar(255) primary_key not_null
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=null)
{ return Memcached_DataObject::staticGet('User_username',$k,$v); }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
}

View File

@ -0,0 +1,54 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Plugin that uses the email address as a username, and checks the password as normal
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 Craig Andrews http://candrews.integralblue.com
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
class EmailAuthenticationPlugin extends Plugin
{
//---interface implementation---//
function onStartCheckPassword($nickname, $password, &$authenticatedUser)
{
if(strpos($nickname, '@'))
{
$user = User::staticGet('email',$nickname);
if($user && isset($user->email))
{
if(common_check_user($user->nickname,$password))
{
$authenticatedUser = $user;
return false;
}
}
}
}
}

View File

@ -0,0 +1,7 @@
The Email Authentication plugin allows users to login using their email address.
The provided email address is used to lookup the user's nickname, then that nickname and the provided password is checked.
Installation
============
add "addPlugin('emailAuthentication');" to the bottom of your config.php

View File

@ -48,20 +48,31 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
public $scope=null; public $scope=null;
public $attributes=array(); public $attributes=array();
function __construct() function onInitializePlugin(){
{ parent::onInitializePlugin();
parent::__construct(); if(!isset($this->host)){
throw new Exception("must specify a host");
}
if(!isset($this->basedn)){
throw new Exception("must specify a basedn");
}
if(!isset($this->attributes['nickname'])){
throw new Exception("must specify a nickname attribute");
}
if(!isset($this->attributes['username'])){
throw new Exception("must specify a username attribute");
}
} }
//---interface implementation---// //---interface implementation---//
function checkPassword($nickname, $password) function checkPassword($username, $password)
{ {
$ldap = $this->ldap_get_connection(); $ldap = $this->ldap_get_connection();
if(!$ldap){ if(!$ldap){
return false; return false;
} }
$entry = $this->ldap_get_user($nickname); $entry = $this->ldap_get_user($username);
if(!$entry){ if(!$entry){
return false; return false;
}else{ }else{
@ -76,48 +87,33 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
} }
} }
function autoRegister($nickname) function autoRegister($username)
{ {
$entry = $this->ldap_get_user($nickname,$this->attributes); $entry = $this->ldap_get_user($username,$this->attributes);
if($entry){ if($entry){
$registration_data = array(); $registration_data = array();
foreach($this->attributes as $sn_attribute=>$ldap_attribute){ foreach($this->attributes as $sn_attribute=>$ldap_attribute){
if($sn_attribute=='email'){ $registration_data[$sn_attribute]=$entry->getValue($ldap_attribute,'single');
$registration_data[$sn_attribute]=common_canonical_email($entry->getValue($ldap_attribute,'single')); }
}else if($sn_attribute=='nickname'){ if(isset($registration_data['email']) && !empty($registration_data['email'])){
$registration_data[$sn_attribute]=common_canonical_nickname($entry->getValue($ldap_attribute,'single')); $registration_data['email_confirmed']=true;
}else{
$registration_data[$sn_attribute]=$entry->getValue($ldap_attribute,'single');
}
} }
//set the database saved password to a random string. //set the database saved password to a random string.
$registration_data['password']=common_good_rand(16); $registration_data['password']=common_good_rand(16);
$user = User::register($registration_data); return User::register($registration_data);
return true;
}else{ }else{
//user isn't in ldap, so we cannot register him //user isn't in ldap, so we cannot register him
return null; return false;
} }
} }
function changePassword($nickname,$oldpassword,$newpassword) function changePassword($username,$oldpassword,$newpassword)
{ {
//TODO implement this //TODO implement this
throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time')); throw new Exception(_('Sorry, changing LDAP passwords is not supported at this time'));
return false; return false;
} }
function canUserChangeField($nickname, $field)
{
switch($field)
{
case 'password':
case 'nickname':
case 'email':
return false;
}
}
//---utility functions---// //---utility functions---//
function ldap_get_config(){ function ldap_get_config(){
@ -159,7 +155,7 @@ class LdapAuthenticationPlugin extends AuthenticationPlugin
*/ */
function ldap_get_user($username,$attributes=array()){ function ldap_get_user($username,$attributes=array()){
$ldap = $this->ldap_get_connection(); $ldap = $this->ldap_get_connection();
$filter = Net_LDAP2_Filter::create($this->attributes['nickname'], 'equals', $username); $filter = Net_LDAP2_Filter::create($this->attributes['username'], 'equals', $username);
$options = array( $options = array(
'scope' => 'sub', 'scope' => 'sub',
'attributes' => $attributes 'attributes' => $attributes

View File

@ -6,7 +6,8 @@ add "addPlugin('ldapAuthentication', array('setting'=>'value', 'setting2'=>'valu
Settings Settings
======== ========
authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check the any other plugins or the internal password database). provider_name*: a unique name for this authentication provider.
authoritative (false): Set to true if LDAP's responses are authoritative (meaning if LDAP fails, do check any other plugins or the internal password database).
autoregistration (false): Set to true if users should be automatically created when they attempt to login. autoregistration (false): Set to true if users should be automatically created when they attempt to login.
email_changeable (true): Are users allowed to change their email address? (true or false) email_changeable (true): Are users allowed to change their email address? (true or false)
password_changeable (true): Are users allowed to change their passwords? (true or false) password_changeable (true): Are users allowed to change their passwords? (true or false)
@ -23,6 +24,7 @@ filter: Default search filter. See http://pear.php.net/manual/en/package.network
scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php scope: Default search scope. See http://pear.php.net/manual/en/package.networking.net-ldap2.connecting.php
attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name attributes: an array with the key being the StatusNet user attribute name, and the value the LDAP attribute name
username*
nickname* nickname*
email email
fullname fullname
@ -37,6 +39,7 @@ Example
Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory. Here's an example of an LDAP plugin configuration that connects to Microsoft Active Directory.
addPlugin('ldapAuthentication', array( addPlugin('ldapAuthentication', array(
'provider_name'=>'Example',
'authoritative'=>true, 'authoritative'=>true,
'autoregistration'=>true, 'autoregistration'=>true,
'binddn'=>'username', 'binddn'=>'username',

View File

@ -0,0 +1,26 @@
The Reverse Username Authentication plugin allows for StatusNet to handle authentication by checking if the provided password is the same as the reverse of the username.
THIS PLUGIN IS FOR TESTING PURPOSES ONLY
Installation
============
add "addPlugin('reverseUsernameAuthentication', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php
Settings
========
provider_name*: a unique name for this authentication provider.
password_changeable*: must be set to false. This plugin does not support changing passwords.
authoritative (false): Set to true if this plugin's responses are authoritative (meaning if this fails, do check any other plugins or the internal password database).
autoregistration (false): Set to true if users should be automatically created when they attempt to login.
* required
default values are in (parenthesis)
Example
=======
addPlugin('reverseUsernameAuthentication', array(
'provider_name'=>'Example',
'password_changeable'=>false,
'authoritative'=>true,
'autoregistration'=>true
));

View File

@ -0,0 +1,58 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Plugin that checks if the password is the reverse of username
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 Craig Andrews http://candrews.integralblue.com
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/plugins/Authentication/AuthenticationPlugin.php';
class ReverseUsernameAuthenticationPlugin extends AuthenticationPlugin
{
//---interface implementation---//
function onInitializePlugin(){
parent::onInitializePlugin();
if(!isset($this->password_changeable) && $this->password_changeable){
throw new Exception("password_changeable cannot be set to true. This plugin does not support changing passwords.");
}
}
function checkPassword($username, $password)
{
return $username == strrev($password);
}
function autoRegister($username)
{
$registration_data = array();
$registration_data['nickname'] = $username ;
return User::register($registration_data);
}
}