diff options
author | Florian Reinhard <florian.reinhard@googlemail.com> | 2012-04-07 17:22:56 +0400 |
---|---|---|
committer | Florian Reinhard <florian.reinhard@googlemail.com> | 2012-04-07 17:22:56 +0400 |
commit | da5b1c8bf768ebfb1a9bc8b62e4859b14a6ff945 (patch) | |
tree | cc9157d1e340d19bc5b13b65c35af02d02f9bfce /django_auth | |
parent | fe19aeba61d0f2760f77d1911d31100bfa00b331 (diff) |
Basic django_auth implementation.
Basic implementation of django authentification backend providing read-only users and groups.
Diffstat (limited to 'django_auth')
-rw-r--r-- | django_auth/appinfo/app.php | 37 | ||||
-rw-r--r-- | django_auth/appinfo/info.xml | 14 | ||||
-rw-r--r-- | django_auth/lib/group.php | 200 | ||||
-rw-r--r-- | django_auth/lib/phpsec.crypt.php | 232 | ||||
-rw-r--r-- | django_auth/lib/user.php | 151 | ||||
-rw-r--r-- | django_auth/settings.php | 43 | ||||
-rw-r--r-- | django_auth/templates/settings.php | 10 |
7 files changed, 687 insertions, 0 deletions
diff --git a/django_auth/appinfo/app.php b/django_auth/appinfo/app.php new file mode 100644 index 000000000..50d04d29a --- /dev/null +++ b/django_auth/appinfo/app.php @@ -0,0 +1,37 @@ +<?php + +/** + * ownCloud - Django Authentification Backend + * + * @author Florian Reinhard + * @copyright 2011 Florian Reinhard <florian.reinhard@googlemail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +require_once('apps/django_auth/lib/user.php'); +require_once('apps/django_auth/lib/group.php'); + +define('OC_GROUP_BACKEND_DJANGO_STAFF_IS_ADMIN', true); +define('OC_GROUP_BACKEND_DJANGO_SUPERUSER_IS_ADMIN', true); + +OC_APP::registerAdmin('django_auth','settings'); + +OC_User::registerBackend('DJANGO_AUTH'); +OC_User::useBackend( 'DJANGO_AUTH' ); + +OC_Group::registerBackend('DJANGO_AUTH'); +OC_Group::setBackend( 'DJANGO_AUTH' ); +?>
\ No newline at end of file diff --git a/django_auth/appinfo/info.xml b/django_auth/appinfo/info.xml new file mode 100644 index 000000000..45acb02a3 --- /dev/null +++ b/django_auth/appinfo/info.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<info> + <id>django_auth</id> + <name>Django Authentification Backend</name> + <description>This is a read-only authentification backend providing django users and groups to owncloud. You won't be able to edit anything. + Adding, editing and deleting of groups or users will just silently fail. So does changing passwords. Use the django.contrib.admin for that kind of tasks. + Owncloud and your django webapplication need to share the same database. + Supported password hashers:SHA1, PBKDF2_SHA1, PBKDF2_SHA256. + </description> + <version>0.1</version> + <licence>AGPL</licence> + <author>Florian Reinhard florian.reinhard@googlemail.com</author> + <require>3</require> +</info> diff --git a/django_auth/lib/group.php b/django_auth/lib/group.php new file mode 100644 index 000000000..2ff10fe77 --- /dev/null +++ b/django_auth/lib/group.php @@ -0,0 +1,200 @@ +<?php + +/** + * ownCloud + * + * @author Florian Reinhard + * @copyright 2012 Florian Reinhard <florian.reinhard@googlemail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * @brief Class providing django groups to ownCloud + * @see http://www.djangoproject.com + * + * Authentification backend to authenticate agains a django webapplication using + * django.contrib.auth. + */ +class OC_GROUP_DJANGO_AUTH extends OC_Group_Backend { + static $staff_is_admin; + static $superuser_is_admin; + + public function __construct() { + self::$staff_is_admin = OC_Appconfig::getValue('django_auth', 'staff_is_admin', OC_GROUP_BACKEND_DJANGO_STAFF_IS_ADMIN); + self::$superuser_is_admin = OC_Appconfig::getValue('django_auth', 'superuser_is_admin', OC_GROUP_BACKEND_DJANGO_SUPERUSER_IS_ADMIN); + } + static private $userGroupCache=array(); + + /** + * @brief Try to create a new group + * @param $gid The name of the group to create + * @returns true/false + * + * Trys to create a new group. If the group name already exists, false will + * be returned. + */ + public static function createGroup( $gid ) { + OC_Log::write('OC_GROUP_DJANGO_AUTH', 'Use the django webinterface to create groups',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief delete a group + * @param $gid gid of the group to delete + * @returns true/false + * + * Deletes a group and removes it from the group_user-table + */ + public static function deleteGroup( $gid ) { + OC_Log::write('OC_GROUP_DJANGO_AUTH', 'Use the django webinterface to delete groups',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief is user in group? + * @param $uid uid of the user + * @param $gid gid of the group + * @returns true/false + * + * Checks whether the user is member of a group or not. + */ + public static function inGroup( $uid, $gid ) { + // Special case for the admin group + if ($gid =='admin') { + $sql = 'SELECT `auth_user`.`username` + FROM `auth_user` + WHERE `auth_user`.`username` = ? + AND `auth_user`.`is_active` = 1'; + if (self::$superuser_is_admin or self::$staff_is_admin) { + $sql.="\nAND ("; + if (self::$superuser_is_admin) { + $sql.='`auth_user`.`is_superuser` = 1'; + if (self::$staff_is_admin) { + $sql.=' OR '; + } + } + if (self::$staff_is_admin) { + $sql.='`auth_user`.`is_staff` = 1'; + } + $sql.=')'; + } + $query = OC_DB::prepare($sql); + $result = $query->execute( array( $uid )); + } + else { + $sql = 'SELECT `auth_user`.`username` + FROM `auth_user` + INNER JOIN `auth_user_groups` + ON (`auth_user`.`id` = `auth_user_groups`.`user_id`) + INNER JOIN `auth_group` + ON (`auth_group`.`id` = `auth_user_groups`.`group_id`) + WHERE `auth_group`.`name` = ? + AND `auth_user`.`username` = ? + AND `auth_user`.`is_active` = 1'; + $query = OC_DB::prepare($sql); + $result = $query->execute( array( $gid, $uid )); + } + + return $result->fetchRow() ? true : false; + } + + /** + * @brief Add a user to a group + * @param $uid Name of the user to add to group + * @param $gid Name of the group in which add the user + * @returns true/false + * + * Adds a user to a group. + */ + public static function addToGroup( $uid, $gid ){ + OC_Log::write('OC_GROUP_DJANGO_AUTH', 'Use the django webinterface to add users to groups',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Removes a user from a group + * @param $uid Name of the user to remove from group + * @param $gid Name of the group from which remove the user + * @returns true/false + * + * removes the user from a group. + */ + public static function removeFromGroup( $uid, $gid ){ + OC_Log::write('OC_GROUP_DJANGO_AUTH', 'Use the django webinterface to remove users from groups',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Get all groups a user belongs to + * @param $uid Name of the user + * @returns array with group names + * + * This function fetches all groups a user belongs to. It does not check + * if the user exists at all. + */ + public static function getUserGroups( $uid ){ + $query = OC_DB::prepare( 'SELECT `auth_group`.`name` +FROM `auth_group` +INNER JOIN `auth_user_groups` ON ( `auth_group`.`id` = `auth_user_groups`.`group_id` ) +INNER JOIN `auth_user` ON ( `auth_user`.`id` = `auth_user_groups`.`user_id` ) + WHERE `auth_user`.`username` = ? + AND `auth_user`.`is_active` = 1' ); + $result = $query->execute( array( $uid )); + $groups = array(); + while ( $row = $result->fetchRow()) { + $groups[] = $row["name"]; + } + return $groups; + } + + /** + * @brief get a list of all groups + * @returns array with group names + * + * Returns a list with all groups + */ + public static function getGroups(){ + $query = OC_DB::prepare( "SELECT id, name FROM auth_group ORDER BY name" ); + $result = $query->execute(); + $groups = array(); + while ( $row = $result->fetchRow()) { + $groups[] = $row["name"]; + } + + return $groups; + } + + /** + * @brief get a list of all users in a group + * @returns array with user ids + */ + public static function usersInGroup($gid) { + $query = OC_DB::prepare('SELECT `auth_user`.`username` + FROM `auth_user` + INNER JOIN `auth_user_groups` + ON (`auth_user`.`id` = `auth_user_groups`.`user_id`) + INNER JOIN `auth_group` + ON (`auth_group`.`id` = `auth_user_groups`.`group_id`) + WHERE `auth_group`.`name` = ? + AND `auth_user`.`is_active` = 1'); + $users = array(); + $result = $query->execute(array($gid)); + while ($row=$result->fetchRow()) { + $users[]=$row['username']; + } + return $users; + } +} diff --git a/django_auth/lib/phpsec.crypt.php b/django_auth/lib/phpsec.crypt.php new file mode 100644 index 000000000..3db662422 --- /dev/null +++ b/django_auth/lib/phpsec.crypt.php @@ -0,0 +1,232 @@ +<?php +/** + phpSec - A PHP security library + + @author Audun Larsen <larsen@xqus.com> + @copyright Copyright (c) Audun Larsen, 2011 + @link https://github.com/phpsec/phpSec + @license http://opensource.org/licenses/mit-license.php The MIT License + @package phpSec + */ + +/** + * Provides methods for encrypting data. + */ +class phpsecCrypt { + public static $_algo = 'rijndael-256'; + public static $_mode = 'ctr'; + public static $_padding = true; + + + const HASH_TYPE = 'sha256'; + + /** + * Encrypt data returning a JSON encoded array safe for storage in a database + * or file. The array has the following structure before it is encoded: + * array( + * 'cdata' => 'Encrypted data, Base 64 encoded', + * 'iv' => 'Base64 encoded IV', + * 'algo' => 'Algorythm used', + * 'mode' => 'Mode used', + * 'mac' => 'Message Authentication Code' + * ) + * + * @param mixed $data + * Data to encrypt. + * + * @param string $key + * Key to encrypt data with. + * + * @return string + * Serialized array containing the encrypted data along with some meta data. + */ + public static function encrypt($data, $key) { + + $td = mcrypt_module_open(self::$_algo, '', self::$_mode, ''); + + /* Check key size. */ + $keySize = strlen($key); + $keySizes = mcrypt_enc_get_supported_key_sizes($td); + if(count($keySizes) > 0) { + /* Encryption method requires a specific key size. */ + if(!in_array($keySize, $keySizes)) { + phpsec::error('Key is out of range. Should be one of: '. var_export($keySizes ,1)); + return false; + } + } else { + /* No spsecific size is needed. */ + if($keySize == 0 || $keySize > mcrypt_enc_get_key_size($td)) { + phpsec::error('Key is out of range. Should be: 1 - ' . mcrypt_enc_get_key_size($td).' bytes.'); + return false; + } + } + + /* Create IV. */ + $iv = phpsecRand::bytes(mcrypt_enc_get_iv_size($td)); + + /* Init mcrypt. */ + mcrypt_generic_init($td, $key, $iv); + + /* Prepeare the array with data. */ + $serializedData = serialize($data); + + /* Add padding if enabled. */ + if(self::$_padding === true) { + $block = mcrypt_enc_get_block_size($td); + $serializedData = self::pad($block, $serializedData); + } + + $encrypted['algo'] = self::$_algo; /* Algorithm used to encrypt. */ + $encrypted['mode'] = self::$_mode; /* Algorithm mode. */ + $encrypted['iv'] = base64_encode($iv); /* Initialization vector, just a bunch of randomness. */ + $encrypted['cdata'] = base64_encode(mcrypt_generic($td, $serializedData)); /* The encrypted data. */ + $encrypted['mac'] = base64_encode( /* The message authentication code. Used to make sure the */ + self::pbkdf2($encrypted['cdata'], $key, 1000, 32) /* message is valid when decrypted. */ + ); + + return json_encode($encrypted); + } + + /** + * Strip PKCS7 padding and decrypt + * data encrypted by encrypt(). + * + * @param string $data + * JSON string containing the encrypted data and meta information in the + * excact format as returned by encrypt(). + * + * @return mixed + * Decrypted data in it's original form. + */ + public static function decrypt($data, $key) { + + /* Decode the JSON string */ + $data = json_decode($data, true); + + $dataStructure = array( + 'algo' => true, + 'mode' => true, + 'iv' => true, + 'cdata' => true, + 'mac' => true, + ); + + if($data === NULL || phpsec::arrayCheck($data, $dataStructure) !== true) { + phpsec::error('Invalid data passed to decrypt()'); + return false; + } + /* Everything looks good so far. Let's continue.*/ + $td = mcrypt_module_open($data['algo'], '', $data['mode'], ''); + $block = mcrypt_enc_get_block_size($td); + + /* Check MAC. */ + if(base64_decode($data['mac']) != self::pbkdf2($data['cdata'], $key, 1000, 32)) { + phpsec::error('Message authentication code invalid'); + return false; + } + + /* Init mcrypt. */ + mcrypt_generic_init($td, $key, base64_decode($data['iv'])); + + $decrypted = rtrim(mdecrypt_generic($td, base64_decode(self::stripPadding($block, $data['cdata'])))); + + /* Close up. */ + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + /*Return decrypted data. */ + return unserialize($decrypted); + + } + + /** + * Implement PBKDF2 as described in RFC 2898. + * + * @param string $p + * Password to protect. + * + * @param string $s + * Salt. + * + * @param integer $c + * Iteration count. + * + * @param integer $dkLen + * Derived key length. + * + * @param string $a + * A hash algorithm. + * + * @return binary + * Derived key. + */ + public static function pbkdf2($p, $s, $c, $dkLen, $a = 'sha256') { + $hLen = strlen(hash($a, null, true)); /* Hash length. */ + $l = ceil($dkLen / $hLen); /* Length in blocks of derived key. */ + $dk = ''; /* Derived key. */ + + /* Step 1. Check dkLen. */ + if($dkLen > (2^32-1) * $hLen) { + phpsec::error('Derived key too long'); + return false; + } + + for ($block = 1; $block<=$l; $block ++) { + /* Initial hash for this block. */ + $ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true); + /* Do block iterations. */ + for ($i = 1; $i<$c; $i ++) { + /* XOR iteration. */ + $ib ^= ($b = hash_hmac($a, $b, $p, true)); + } + /* Append iterated block. */ + $dk .= $ib; + } + /* Returned derived key. */ + return substr($dk, 0, $dkLen); + } + + /** + * PKCS7-pad data. + * Add bytes of data to fill up the last block. + * PKCS7 padding adds bytes with the same value that the number of bytes that are added. + * @see http://tools.ietf.org/html/rfc5652#section-6.3 + * + * @param integer $block + * Block size. + * + * @param string $data + * Data to pad. + * + * @return string + * Padded data. + */ + public static function pad($block, $data) { + $pad = $block - (strlen($data) % $block); + $data .= str_repeat(chr($pad), $pad); + + return $data; + } + + /** + * Strip PKCS7-padding. + * + * @param integer $block + * Block size. + * + * @param string $data + * Padded data. + * + * @return string + * Original data. + */ + public static function stripPadding($block, $data) { + $pad = ord($data[($len = strlen($data)) - 1]); + + /* Check that what we have at the end of the string really is padding, and if it is remove it. */ + if ($pad && $pad < $block && preg_match('/' . chr($pad) . '{' . $pad . '}$/', $data)) { + return substr($data, 0, -$pad); + } + return $data; + } +} diff --git a/django_auth/lib/user.php b/django_auth/lib/user.php new file mode 100644 index 000000000..977165758 --- /dev/null +++ b/django_auth/lib/user.php @@ -0,0 +1,151 @@ +<?php + +/** + * ownCloud - Django Authentification Backend + * + * @author Florian Reinhard + * @copyright 2011 Florian Reinhard <florian.reinhard@googlemail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +require_once('phpsec.crypt.php'); + +/** + * @brief Class providing django users to ownCloud + * @see http://www.djangoproject.com + * + * Authentification backend to authenticate agains a django webapplication using + * django.contrib.auth. + */ +class OC_USER_DJANGO_AUTH extends OC_User_Backend { + /** + * @brief Create a new user + * @param $uid The username of the user to create + * @param $password The password of the new user + * @returns true/false + * + * Creates a new user. Basic checking of username is done in OC_User + * itself, not in its subclasses. + */ + public function createUser($uid, $password){ + OC_Log::write('OC_USER_DJANGO_AUTH', 'Use the django webinterface to create users',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief delete a user + * @param $uid The username of the user to delete + * @returns true/false + * + * Deletes a user + */ + public function deleteUser( $uid ){ + OC_Log::write('OC_USER_DJANGO_AUTH', 'Use the django webinterface to delete users',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Set password + * @param $uid The username + * @param $password The new password + * @returns true/false + * + * Change the password of a user + */ + public function setPassword($uid, $password){ + OC_Log::write('OC_USER_DJANGO_AUTH', 'Use the django webinterface to change passwords',3); + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns true/false + * + * Check if the password is correct without logging in the user + */ + private function beginsWith($str,$sub){ + return ( substr( $str, 0, strlen( $sub ) ) === $sub ); + } + public function checkPassword($uid, $password){ + $query = OC_DB::prepare( 'SELECT username, password FROM auth_user WHERE username = ?' ); + $result = $query->execute( array( $uid)); + $row = $result->fetchRow(); + if ($row) { + $storedHash=$row['password']; + if (self::beginsWith($storedHash, 'sha1')){ + $chunks = preg_split('/\$/', $storedHash,3); + $salt = $chunks[1]; + $hash = $chunks[2]; + + if (sha1($salt.$password) === $hash) + return $uid; + else + return false; + } + elseif (self::beginsWith($storedHash, 'pbkdf2')) { + $chunks = preg_split('/\$/', $storedHash,4); + list($pbkdf, $algorithm) = preg_split('/_/', $chunks[0]); + $iter = $chunks[1]; + $salt = $chunks[2]; + $hash = $chunks[3]; + + if ($algorithm === 'sha1') + $digest_size = 20; + elseif ($algorithm === 'sha256') + $digest_size = 32; + else + return false; + + if (base64_encode (phpsecCrypt::pbkdf2($password, $salt, $iter, $digest_size, $algorithm)) === $hash) + return $uid; + else + return false; + } + } + else { + return false; + } + } + + /** + * @brief Get a list of all users + * @returns array with all active usernames + * + * Get a list of all users. + */ + public function getUsers(){ + $query = OC_DB::prepare( 'SELECT id, username, is_active FROM `auth_user` WHERE is_active=1 ORDER BY username' ); + $result = $query->execute(); + $users = array(); + while ( $row = $result->fetchRow()) { + $users[] = $row['username']; + } + return $users; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid){ + $query = OC_DB::prepare( 'SELECT username FROM `auth_user` WHERE username = ? AND is_active=1' ); + $result = $query->execute( array( $uid )); + return $result->numRows() > 0; + } +} diff --git a/django_auth/settings.php b/django_auth/settings.php new file mode 100644 index 000000000..36656f5ce --- /dev/null +++ b/django_auth/settings.php @@ -0,0 +1,43 @@ +<?php +/** + * ownCloud - Django Authentification Backend + * + * @author Florian Reinhard + * @copyright 2011 Florian Reinhard <florian.reinhard@googlemail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +$params = array('staff_is_admin', 'superuser_is_admin'); + +if ($_POST) { + foreach($params as $param){ + if(isset($_POST[$param])){ + OC_Appconfig::setValue('django_auth', $param, $_POST[$param]); + } + else { + // unchecked checkboxes are not included in the post paramters + OC_Appconfig::setValue('django_auth', $param, 0); + } + } +} + +// fill template +$tmpl = new OC_Template( 'django_auth', 'settings'); +$tmpl->assign('staff_is_admin', OC_Appconfig::getValue( 'django_auth', 'staff_is_admin', OC_GROUP_BACKEND_DJANGO_STAFF_IS_ADMIN )); +$tmpl->assign('superuser_is_admin',OC_Appconfig::getValue( 'django_auth', 'superuser_is_admin', OC_GROUP_BACKEND_DJANGO_SUPERUSER_IS_ADMIN )); + +return $tmpl->fetchPage(); +?>
\ No newline at end of file diff --git a/django_auth/templates/settings.php b/django_auth/templates/settings.php new file mode 100644 index 000000000..3ed9c7c2f --- /dev/null +++ b/django_auth/templates/settings.php @@ -0,0 +1,10 @@ +<form id="django_auth" action="#" method="post"> + <fieldset class="personalblock"> + <legend><strong>Django Authentification Backend</strong></legend> + <p> + <input type="checkbox" name="staff_is_admin" id="staff_is_admin" <?php if ($_['staff_is_admin'] == true){ echo 'checked="checked"';} ?>/><label for="staff_is_admin" ><?php echo $l->t('Django Staffusers get administration privileges');?></label><br/> + <input type="checkbox" name="superuser_is_admin" id="superuser_is_admin" <?php if ($_['superuser_is_admin'] == true){ echo 'checked="checked"';} ?>/><label for="superuser_is_admin"><?php echo $l->t('Django Superusers get administration privileges');?></label> + </p> + <input type="submit" name="django_auth" value="Save" /> + </fieldset> +</form> |