diff --git a/crowd/crowd.php b/crowd/crowd.php index 58ce7d8..793b490 100644 --- a/crowd/crowd.php +++ b/crowd/crowd.php @@ -5,17 +5,17 @@ * @subpackage Plugins * @license GNU/GPL */ - + // Check to ensure this file is included in Joomla! defined('_JEXEC') or die(); - + jimport('joomla.log.log'); jimport('joomla.plugin.plugin'); jimport('joomla.event.plugin'); jimport('joomla.error.log'); jimport('joomla.client.http'); - + class plgAuthenticationCrowd extends JPlugin { /** @@ -28,268 +28,291 @@ class plgAuthenticationCrowd extends JPlugin * @param object $subject The object to observe * @since 1.5 */ - function plgAuthenticationCrowd(& $subject, $config) { + function plgAuthenticationCrowd(& $subject, $config) + { parent::__construct($subject, $config); JLog::addLogger(array('text_file' => 'debug.crowd.log')); JLog::add('crowd 0.01 Start logging'); //JLog::add('crowd config params: ' . $this->params); } - - // see rules.php - protected function getUserGroups() - { - // Initialise variables. - $db = JFactory::getDBO(); - $query = $db->getQuery(true); - $query->select('a.id AS value, a.title AS text') + + // see rules.php + protected function getUserGroups() + { + // Initialise variables. + $db = JFactory::getDBO(); + $query = $db->getQuery(true); + $query->select('a.id AS value, a.title AS text') ->from('#__usergroups AS a'); - $db->setQuery($query); - $options = $db->loadObjectList(); - - return $options; - } - - /** after a failed login check if this user does not exists on crowd and delete it in this case */ - protected function checkDeleteUser($credentials) - { - JLog::add('check if we have to delete user ' . $credentials['username']); - $id = intval(JUserHelper::getUserId($credentials['username'])); - if ($id < 1) { - JLog::add('user not known to joomla, do nothing'); - return; // no such user in joomla, nothing to do for us - } - - // check whether this user exists in crowd - $server = $this->params->get('crowd_url'); - $appname = $this->params->get('crowd_app_name'); - $apppass = $this->params->get('crowd_password'); - JLog::add('onUserAuthenticate: connecting to url ' . $server); - $authcode = base64_encode($appname . ":" . $apppass); - // JLog::add('auth code [' . $authcode . ']'); - - // request cookie config from crowd - $request_url = $server . '/rest/usermanagement/1/user?username=' . $credentials['username']; - $request_header = array('Accept' => 'application/json', 'Content-type' => 'application/xml', - 'Authorization' => 'Basic ' . $authcode); - $http = new JHttp; - JLog::add('request url ' . $request_url); - JLog::add('with headers ' . var_export($request_header, true)); - $result = $http->get($request_url, $request_header); - JLog::add('response: ' . var_export($result, true)); - $obj = json_decode($result->body); - JLog::add('msg: ' . $obj->reason); - if ($obj->reason != 'USER_NOT_FOUND') { - JLog::add('crowd seems to know about this user, do not delete from joomla!'); - return; + $db->setQuery($query); + $options = $db->loadObjectList(); + + return $options; + } + + /** after a failed login check if this user does not exists on crowd and delete it in this case */ + protected function checkDeleteUser($credentials) + { + JLog::add('check if we have to delete user ' . $credentials['username']); + $id = intval(JUserHelper::getUserId($credentials['username'])); + if ($id < 1) { + JLog::add('user not known to joomla, do nothing'); + return; // no such user in joomla, nothing to do for us + } + + // check whether this user exists in crowd + $server = $this->params->get('crowd_url'); + $appname = $this->params->get('crowd_app_name'); + $apppass = $this->params->get('crowd_password'); + JLog::add('onUserAuthenticate: connecting to url ' . $server); + $authcode = base64_encode($appname . ":" . $apppass); + // JLog::add('auth code [' . $authcode . ']'); + + // request cookie config from crowd + $request_url = $server . '/rest/usermanagement/1/user?username=' . $credentials['username']; + $request_header = $this->buildJSONRequestHeaders($authcode); + $http = new JHttp; + JLog::add('request url ' . $request_url); + JLog::add('with headers ' . var_export($request_header, true)); + $result = $http->get($request_url, $request_header); + JLog::add('response: ' . var_export($result, true)); + $obj = json_decode($result->body); + JLog::add('msg: ' . $obj->reason); + if ($obj->reason != 'USER_NOT_FOUND') { + JLog::add('crowd seems to know about this user, do not delete from joomla!'); + return; + } + + // delete this user + $user = JUser::getInstance(); + $user->load($id); + $user->delete(); + JLog::add('user deleted from joomla.'); + } + + /** + * @param $authcode + * @return array + */ + private function buildJSONRequestHeaders($authcode) + { + $request_header = array('Accept' => 'application/json', 'Content-type' => 'application/xml', + 'Authorization' => 'Basic ' . $authcode, + 'Connection' => 'close'); + return $request_header; } - - // delete this user - $user = JUser::getInstance(); - $user->load($id); - $user->delete(); - JLog::add('user deleted from joomla.'); - } - - /** creates a new joomla group, returns the resulting group id */ - protected function createGroup($gname) - { - //$db = JFactory::getDbo(); - JLog::add('createGroup not yet implemented!'); - } - - + + private function buildXMLRequestHeaders($authcode) { + $request_header = array('Accept' => 'application/xml', 'Content-type' => 'application/xml', + 'Authorization' => 'Basic ' . $authcode, + 'Connection' => 'close'); + return $request_header; + } + + /** creates a new joomla group, returns the resulting group id */ + protected function createGroup($gname) + { + //$db = JFactory::getDbo(); + JLog::add('createGroup not yet implemented!'); + } + + /** Trust the sso flag, login without password */ - function doSSOLogin( $credentials, $options, &$response ) + function doSSOLogin($credentials, $options, &$response) { - JLog::add('crowd::onUserAuthenticate immediate login: ' . var_export($credentials, true)); - $response->status = JAuthentication::STATUS_SUCCESS; - $response->email = $credentials['email']; - $response->fullname = $credentials['fullname']; - $response->username = $credentials['username']; - $response->error_message = ''; - JLog::add('crowd: returning response: ' . var_export($response, true)); - return true; + JLog::add('crowd::onUserAuthenticate immediate login: ' . var_export($credentials, true)); + $response->status = JAuthentication::STATUS_SUCCESS; + $response->email = $credentials['email']; + $response->fullname = $credentials['fullname']; + $response->username = $credentials['username']; + $response->error_message = ''; + JLog::add('crowd: returning response: ' . var_export($response, true)); + return true; } - - /** checks username/password against crowd */ - function doCrowdLogin( $credentials, $options, &$response ) + + /** checks username/password against crowd */ + function doCrowdLogin($credentials, $options, &$response) { - JLog::add('doing regular login'); - - $server = $this->params->get('crowd_url'); - $appname = $this->params->get('crowd_app_name'); - $apppass = $this->params->get('crowd_password'); - JLog::add('onUserAuthenticate: connecting to url ' . $server); - $authcode = base64_encode($appname . ":" . $apppass); - // JLog::add('auth code [' . $authcode . ']'); - - // request cookie config from crowd - $request_url = $server . '/rest/usermanagement/1/config/cookie'; - $request_header = array('Accept' => 'application/json', 'Content-type' => 'application/xml', - 'Authorization' => 'Basic ' . $authcode); - $http = new JHttp; - JLog::add('request url ' . $request_url); - JLog::add('with headers ' . var_export($request_header, true)); - $result = $http->get($request_url, $request_header); - JLog::add('cookie config: ' . var_export($result, true)); - if (!$result or $result->code != 200) { - $response->status = JAUTHENTICATE_STATUS_FAILURE; - $response->error_message = 'Cannot fetch cookie config from crowd'; - return false; - } - $obj = json_decode($result->body); - $cookieName = $obj->name; - $cookieDomain = $obj->domain; - JLog::add('cookie name: ' . $cookieName . ', domain: ' . $cookieDomain); - - // now trying to login - $request_url = $server . '/rest/usermanagement/1/session?validate-password=true'; - JLog::add('request url ' . $request_url); - $request_data = '' . - '' . - ' ' . $credentials['username'] . '' . - ' ' . $credentials['password'] . '' . - ' ' . - ' ' . - ' remote_address' . - ' ' . $_SERVER['REMOTE_ADDR'] . '' . - ' ' . - ' ' . - ''; - #JLog::add('request data: ' . $request_data); - $request_header = array('Accept' => 'application/xml', 'Content-type' => 'application/xml', - 'Authorization' => 'Basic ' . $authcode); - JLog::add('with headers ' . var_export($request_header, true)); - $result = $http->post($request_url, $request_data, $request_header); - JLog::add('response: ' . var_export($result, true)); - if (!$result or $result->code != 201) { - JLog::add('have not got expected code 201, login failed'); - $response->status = JAUTHENTICATE_STATUS_FAILURE; - $response->error_message = 'Login to crowd failed'; - return false; - } - JLog::add('fetching info from new location: ' . $result->headers['Location']); - $result = $http->get($result->headers['Location'], $request_header); - JLog::add('response: ' . var_export($result, true)); - if (!$result or $result->code != 200) { - JLog::add('have not got expected code 200, login failed'); - $response->status = JAUTHENTICATE_STATUS_FAILURE; - $response->error_message = 'Login to crowd failed'; - return false; - } - $xml = new SimpleXMLElement($result->body); - $token = (string) $xml->token; - JLog::add('token: ' . $token); - - // set response values for joomla auth - $response->email = (string) $xml->user->email; - $response->fullname = (string) $xml->user->{'display-name'}; - $response->username = (string) $xml->user['name']; - $response->status = JAUTHENTICATE_STATUS_SUCCESS; - $response->error_message = ''; - JLog::add('login successfull, returning: ' . var_export($response, true)); - - // finally export our token as cookie - JLog::add('set cookie ' . $cookieName . ' = ' . $token); - setcookie($cookieName,$token, 0, "/", $cookieDomain,false,true); - - return true; + JLog::add('doing regular login'); + + $server = $this->params->get('crowd_url'); + $appname = $this->params->get('crowd_app_name'); + $apppass = $this->params->get('crowd_password'); + JLog::add('onUserAuthenticate: connecting to url ' . $server); + $authcode = base64_encode($appname . ":" . $apppass); + // JLog::add('auth code [' . $authcode . ']'); + + // request cookie config from crowd + $request_url = $server . '/rest/usermanagement/1/config/cookie'; + $request_header = $this->buildJSONRequestHeaders($authcode); + $http = new JHttp; + JLog::add('request url ' . $request_url); + JLog::add('with headers ' . var_export($request_header, true)); + $result = $http->get($request_url, $request_header); + JLog::add('cookie config: ' . var_export($result, true)); + if (!$result or $result->code != 200) { + $response->status = JAUTHENTICATE_STATUS_FAILURE; + $response->error_message = 'Cannot fetch cookie config from crowd'; + return false; + } + $obj = json_decode($result->body); + $cookieName = $obj->name; + $cookieDomain = $obj->domain; + + // if we have an alternal cookie domain, set it now. + $altCookieDomain = $this->params->get('crowd_cookie_domain'); + if (is_string($altCookieDomain)) { + $cookieDomain = $altCookieDomain; + } + + JLog::add('cookie name: ' . $cookieName . ', domain: ' . $cookieDomain); + + // now trying to login + $request_url = $server . '/rest/usermanagement/1/session?validate-password=true'; + JLog::add('request url ' . $request_url); + $request_data = '' . + '' . + ' ' . $credentials['username'] . '' . + ' ' . $credentials['password'] . '' . + ' ' . + ' ' . + ' remote_address' . + ' ' . $_SERVER['REMOTE_ADDR'] . '' . + ' ' . + ' ' . + ''; + #JLog::add('request data: ' . $request_data); + $request_header = $this->buildXMLRequestHeaders($authcode); + + JLog::add('with headers ' . var_export($request_header, true)); + $result = $http->post($request_url, $request_data, $request_header); + JLog::add('response: ' . var_export($result, true)); + if (!$result or $result->code != 201) { + JLog::add('have not got expected code 201, login failed'); + $response->status = JAUTHENTICATE_STATUS_FAILURE; + $response->error_message = 'Account credentials are not valid or account does not exist'; + return false; + } + JLog::add('fetching info from new location: ' . $result->headers['Location']); + $result = $http->get($result->headers['Location'], $request_header); + JLog::add('response: ' . var_export($result, true)); + if (!$result or $result->code != 200) { + JLog::add('have not got expected code 200, login failed'); + $response->status = JAUTHENTICATE_STATUS_FAILURE; + $response->error_message = 'Account credentials are not valid or account does not exist'; + return false; + } + $xml = new SimpleXMLElement($result->body); + $token = (string)$xml->token; + JLog::add('token: ' . $token); + + // set response values for joomla auth + $response->email = (string)$xml->user->email; + $response->fullname = (string)$xml->user->{'display-name'}; + $response->username = (string)$xml->user['name']; + $response->status = JAuthentication::STATUS_SUCCESS; + $response->error_message = ''; + JLog::add('login successfull, returning: ' . var_export($response, true)); + + // finally export our token as cookie + JLog::add('set cookie ' . $cookieName . ' = ' . $token); + setcookie($cookieName, $token, 0, "/", $cookieDomain, false, true); + + return true; } - - function handleGroups( $user, $credentials, $options, &$response ) + + function handleGroups($user, $credentials, $options, &$response) { - JLog::add('login succeeded, obtaining groups'); - $server = $this->params->get('crowd_url'); - $appname = $this->params->get('crowd_app_name'); - $apppass = $this->params->get('crowd_password'); - JLog::add('handleGroups: connecting to url ' . $server); - $authcode = base64_encode($appname . ":" . $apppass); - // JLog::add('auth code [' . $authcode . ']'); - - // request groups from crowd - $request_url = $server . '/rest/usermanagement/1/user/group/direct?username=' . $credentials['username']; - $request_header = array('Accept' => 'application/json', 'Content-type' => 'application/xml', - 'Authorization' => 'Basic ' . $authcode); - $http = new JHttp; - JLog::add('request url ' . $request_url); - JLog::add('with headers ' . var_export($request_header, true)); - $result = $http->get($request_url, $request_header); - JLog::add('group config: ' . var_export($result, true)); - if (!$result or $result->code != 200) { - $response->status = JAUTHENTICATE_STATUS_FAILURE; - $response->error_message = 'Cannot fetch groups from crowd'; - return false; - } - $obj = json_decode($result->body); - $groups = $obj->groups; // array containing all crowd groups of this user as objects with attr 'name' - JLog::add('crowd groups: ' . var_export($groups, true)); - $response->groups = array(); - $allgroups = $this->getUserGroups(); // array of objects containing group name and joomla id as 'text' and 'value' - JLog::add('joomla groups: ' . var_export($allgroups, true)); - $allgroupnames = array(); // array of joomla group names - - $groupmapping = explode(";",$this->params->get('crowd_group_map')); - $groupmap = array(); - foreach ($groupmapping as $gm) { - $ml = explode(":",$gm); - if (count($ml) != 2 || intval($ml[1]) < 1) { - JLog::add('ignored mapping entry [' . $gm . ']'); - } - else { - $groupmap[trim($ml[0])] = intval($ml[1]); - } - } - JLog::add('groupmap: ' . var_export($groupmap,true)); - - foreach ($allgroups as $jgroup) { - array_push($allgroupnames, $jgroup->text); - } - - // first remove user from all groups - foreach ($allgroups as $group) { - $res = JUserHelper::removeUserFromGroup($user->id, $group->value); - JLog::add('removed user from group ' . $group->text . ' with result ' . $res); - } - - // now re-add all groups we have got from crowd - foreach ($groups as $group) { - JLog::add('got group: ' . $group->name); - array_push($response->groups, $group->name); - //array_push($user->groups, $group->name); - - // check mapping - if (array_key_exists($group->name, $groupmap)) { - $res = JUserHelper::addUserToGroup($user->id, $groupmap[$group->name]); - JLog::add('added user to mapped group ' . $group->name . ' result: ' . $res); - continue; - } - - // create new groups if needed - if (!in_array($group->name, $allgroupnames)) { - JLog::add('create new group ' . $group->name); - $gid = $this->createGroup($group->name); - } - else { // group already exists in joomla - foreach ($allgroups as $g) { - if ($g->text == $group->name) { - try { - JUserHelper::addUserToGroup($user->id, $g->value); - JLog::add("added user " . $user->id . ' to group ' . $g->name . ' id: ' . $g->value); - } catch (Exception $e) { - JLog::add('adding user ' . $user->id . ' to group ' . $g->name . ' caused exception: ' . $e->getMessage()); - } - } - } - } - } - try { - $user->save(); - } catch (Exception $e) { - JLog::add('saving user ' . $user->id . ' caused exception: ' . $e->getMessage()); - } + JLog::add('login succeeded, obtaining groups'); + $server = $this->params->get('crowd_url'); + $appname = $this->params->get('crowd_app_name'); + $apppass = $this->params->get('crowd_password'); + JLog::add('handleGroups: connecting to url ' . $server); + $authcode = base64_encode($appname . ":" . $apppass); + // JLog::add('auth code [' . $authcode . ']'); + + // request groups from crowd + $request_url = $server . '/rest/usermanagement/1/user/group/direct?username=' . $credentials['username']; + $request_header = $this->buildJSONRequestHeaders($authcode); + + $http = new JHttp; + JLog::add('request url ' . $request_url); + JLog::add('with headers ' . var_export($request_header, true)); + $result = $http->get($request_url, $request_header); + JLog::add('group config: ' . var_export($result, true)); + if (!$result or $result->code != 200) { + $response->status = JAUTHENTICATE_STATUS_FAILURE; + $response->error_message = 'Cannot fetch groups from crowd'; + return false; + } + $obj = json_decode($result->body); + $groups = $obj->groups; // array containing all crowd groups of this user as objects with attr 'name' + JLog::add('crowd groups: ' . var_export($groups, true)); + $response->groups = array(); + $allgroups = $this->getUserGroups(); // array of objects containing group name and joomla id as 'text' and 'value' + JLog::add('joomla groups: ' . var_export($allgroups, true)); + $allgroupnames = array(); // array of joomla group names + + $groupmapping = explode(";", $this->params->get('crowd_group_map')); + $groupmap = array(); + foreach ($groupmapping as $gm) { + $ml = explode(":", $gm); + if (count($ml) != 2 || intval($ml[1]) < 1) { + JLog::add('ignored mapping entry [' . $gm . ']'); + } else { + $groupmap[trim($ml[0])] = intval($ml[1]); + } + } + JLog::add('groupmap: ' . var_export($groupmap, true)); + + foreach ($allgroups as $jgroup) { + array_push($allgroupnames, $jgroup->text); + } + + // first remove user from all groups + foreach ($allgroups as $group) { + $res = JUserHelper::removeUserFromGroup($user->id, $group->value); + JLog::add('removed user from group ' . $group->text . ' with result ' . $res); + } + + // now re-add all groups we have got from crowd + foreach ($groups as $group) { + JLog::add('got group: ' . $group->name); + array_push($response->groups, $group->name); + //array_push($user->groups, $group->name); + + // check mapping + if (array_key_exists($group->name, $groupmap)) { + $res = JUserHelper::addUserToGroup($user->id, $groupmap[$group->name]); + JLog::add('added user to mapped group ' . $group->name . ' result: ' . $res); + continue; + } + + // create new groups if needed + if (!in_array($group->name, $allgroupnames)) { + JLog::add('create new group ' . $group->name); + $gid = $this->createGroup($group->name); + } else { // group already exists in joomla + foreach ($allgroups as $g) { + if ($g->text == $group->name) { + try { + JUserHelper::addUserToGroup($user->id, $g->value); + JLog::add("added user " . $user->id . ' to group ' . $g->name . ' id: ' . $g->value); + } catch (Exception $e) { + JLog::add('adding user ' . $user->id . ' to group ' . $g->name . ' caused exception: ' . $e->getMessage()); + } + } + } + } + } + try { + $user->save(); + } catch (Exception $e) { + JLog::add('saving user ' . $user->id . ' caused exception: ' . $e->getMessage()); + } } - + /** * This method should handle any authentication and report back to the subject * @@ -300,43 +323,43 @@ function handleGroups( $user, $credentials, $options, &$response ) * @return boolean * @since 1.5 */ - function onUserAuthenticate( $credentials, $options, &$response ) + function onUserAuthenticate($credentials, $options, &$response) { - JLog::add('onUserAuthenticate: auth started'); - $response->type = 'Crowd'; - $response->password_clear = ""; - $login_succeeded = false; - if (array_key_exists('immediate', $options) and $options['immediate']) { - $login_succeeded = $this->doSSOLogin($credentials, $options, $response); - } - else { - $login_succeeded = $this->doCrowdLogin($credentials, $options, $response); - } - if ($credentials['username'] == "admin") { - JLog::add('admin login, neither check user nor groups'); - return login_succeeded; // do not more for admin user - } - if (! $login_succeeded) { - $this->checkDeleteUser($credentials); - return false; - } - $user = JUser::getInstance(); - if ($id = intval(JUserHelper::getUserId($response->username))) { - } - else { - JLog::add('creating new user ' . $response->username); - $user->set('id', 0); - $user->set('name', $response->fullname); - $user->set('username', $response->username); - $user->set('email', $response->email); - $user->save(); - $id = intval(JUserHelper::getUserId($response->username)); - } - $user->id = $id; - $response->id = $user->id; - - $this->handleGroups($user, $credentials, $options, $response); - return true; + JLog::add('onUserAuthenticate: auth started'); + $response->type = 'Crowd'; + $response->password_clear = ""; + $login_succeeded = false; + if (array_key_exists('immediate', $options) and $options['immediate']) { + $login_succeeded = $this->doSSOLogin($credentials, $options, $response); + } else { + $login_succeeded = $this->doCrowdLogin($credentials, $options, $response); + } + if ($credentials['username'] == "admin") { + JLog::add('admin login, neither check user nor groups'); + return login_succeeded; // do not more for admin user + } + if (!$login_succeeded) { + $this->checkDeleteUser($credentials); + return false; + } + $user = JUser::getInstance(); + if ($id = intval(JUserHelper::getUserId($response->username))) { + } else { + JLog::add('creating new user ' . $response->username); + $user->set('id', 0); + $user->set('name', $response->fullname); + $user->set('username', $response->username); + $user->set('email', $response->email); + $user->save(); + $id = intval(JUserHelper::getUserId($response->username)); + $user = JUser::getInstance($id); + + } + $user->id = $id; + $response->id = $user->id; + + $this->handleGroups($user, $credentials, $options, $response); + return true; } } ?> diff --git a/crowd/crowd.xml b/crowd/crowd.xml index 96558cf..ebd7bf1 100644 --- a/crowd/crowd.xml +++ b/crowd/crowd.xml @@ -31,6 +31,11 @@ label="Application password" size="40" /> +