From 7b3f754add95cc42ea4276a468c23e02b4610e34 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 14 Sep 2007 17:04:11 +0000 Subject: [PATCH] mlanthaler: Initial import of the OpenID authenticator and form class. OpenIDAuthenticator_Controller not yet implemented. (merged from branches/gsoc) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@41769 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- security/MemberLoginForm.php | 16 ++- security/OpenIDAuthenticator.php | 219 +++++++++++++++++++++++++++++++ security/OpenIDLoginForm.php | 140 ++++++++++++++++++++ security/Security.php | 86 +++++++++++- 4 files changed, 449 insertions(+), 12 deletions(-) create mode 100644 security/OpenIDAuthenticator.php create mode 100644 security/OpenIDLoginForm.php diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index aa83d88e2..fa54d0e84 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -3,6 +3,7 @@ * Log-in form for the "member" authentication method */ class MemberLoginForm extends Form { + /** * Constructor * @@ -97,7 +98,7 @@ class MemberLoginForm extends Form { /** * Log out * - * @todo Figure out for what this method is used! + * @todo Figure out for what this method is used! Is it really used at all? */ public function logout(){ $s = new Security(); @@ -105,16 +106,19 @@ class MemberLoginForm extends Form { } - /** - * Check the membership - * - * If one of them or both don't match, set the fields which are unmatched with red star * - */ + /** + * Try to authenticate the user + * + * @param array Submitted data + * @return Member Returns the member object on successful authentication + * or NULL on failure. + */ public function performLogin($data){ if($member = MemberAuthenticator::authenticate($data)) { $firstname = Convert::raw2xml($member->FirstName); $this->sessionMessage("Welcome Back, {$firstname}", "good"); $member->LogIn(); + if(isset($data['Remember'])) { // Deliberately obscure... Cookie::set('alc_enc',base64_encode("$data[Email]:$data[Password]")); diff --git a/security/OpenIDAuthenticator.php b/security/OpenIDAuthenticator.php new file mode 100644 index 000000000..375e1fb1e --- /dev/null +++ b/security/OpenIDAuthenticator.php @@ -0,0 +1,219 @@ + + */ +class OpenIDAuthenticator extends Authenticator { + + /** + * Method to authenticate an user + * + * @param array $RAW_data Raw data to authenticate the user + * @return bool|Member Returns FALSE if authentication fails, otherwise + * the member object + */ + public function authenticate(array $RAW_data) { + $openid = $RAW_data['OpenIDURL']; + + $trust_root = Director::absoluteBaseURL(); + $return_to_url = $trust_root . 'OpenIDAuthenticator_Controller'; + + /** + * @todo Change the store to use the database! + */ + // FIXXXME + $store_path = TEMP_FOLDER; + + if(!file_exists($store_path) && !mkdir($store_path)) { + print "Could not create the FileStore directory '$store_path'. ". + " Please check the effective permissions."; + exit(0); + } + $store = new Auth_OpenID_FileStore($store_path); + // END FIXXXME + $consumer = new Auth_OpenID_Consumer($store, new SessionWrapper()); + + + // Begin the OpenID authentication process. + $auth_request = $consumer->begin($openid); + + // No auth request means we can't begin OpenID. + if(!$auth_request) { + displayError("Authentication error; not a valid OpenID."); + } + + + /** + * @todo Check if the POST request should be send directly (without rendering a form) + */ + // For OpenID 1, send a redirect. For OpenID 2, use a Javascript + // form to send a POST request to the server. + if($auth_request->shouldSendRedirect()) { + $redirect_url = $auth_request->redirectURL($trust_root, $return_to_url); + + // If the redirect URL can't be built, display an error + // message. + if(Auth_OpenID::isFailure($redirect_url)) { + displayError("Could not redirect to server: " . $redirect_url->message); + } else { + Director::redirect($redirect_url); +// header("Location: ".$redirect_url); + + } + } else { + // Generate form markup and render it. + $form_id = 'openid_message'; + $form_html = $auth_request->formMarkup($trust_root, $return_to_url, + false, array('id' => $form_id)); + + // Display an error if the form markup couldn't be generated; + // otherwise, render the HTML. + if(Auth_OpenID::isFailure($form_html)) { + displayError("Could not redirect to server: " . $form_html->message); + } else { + $page_contents = array( + "", + "OpenID transaction in progress", + "", + "", + $form_html, + ""); + + print implode("\n", $page_contents); + } + } + exit(); + } + + + /** + * Method that creates the login form for this authentication method + * + * @return Form Returns the login form to use with this authentication + * method + */ + public function getLoginForm() { + return Object::create("OpenIDLoginForm", $this, "LoginForm"); + } +} + + + +/** + * OpenIDAuthenticator Controller + * + * This class handles the response of the OpenID server to authenticate + * the user + * + * @author Markus Lanthaler + */ +class OpenIDAuthenticator_Controller extends Controller { + + /** + * Run the controller + */ + function run($requestParams) { + parent::init(); + + if(isset($_GET['debug_profile'])) Profiler::mark("OpenIDAuthenticator_Controller"); + + die("Not implemented yet!"); + + if(isset($_GET['debug_profile'])) Profiler::unmark("OpenIDAuthenticator_Controller"); + } + + + /** + * Helper function to set a session message for the OpenID login form + * + * @param string $message Message to store + * @param string $type Message type (e.g. "good" or "bad") + */ + function sessionMessage($message, $type) { + Session::set("FormInfo.OpenIDLoginForm_LoginForm.formError.message", $message); + Session::set("'FormInfo.OpenIDLoginForm_LoginForm.formError.type", $type); + } + +} + + + +/** + * Session wrapper class for the OpenID library + * + * This class is a wrapper for the {@link Session} which implements the + * interface of the {@link Auth_Yadis_PHPSession} class. + * + * @author Markus Lanthaler + */ +class SessionWrapper { + + /** + * Set a session key/value pair. + * + * @param string $name The name of the session key to add. + * @param string $value The value to add to the session. + */ + public function set($name, $value) { + Session::set($name, $value); + } + + + /** + * Get a key's value from the session. + * + * @param string $name The name of the key to retrieve. + * @param string $default The optional value to return if the key + * is not found in the session. + * @return string $result The key's value in the session or + * $default if it isn't found. + */ + public function get($name, $default=null) { + $value = Session::get($name); + if(is_null($value)) + $value = $default; + + return $value; + } + + + /** + * Remove a key/value pair from the session. + * + * @param string $name The name of the key to remove. + */ + public function del($name) { + Session::clear($name); + } + + + /** + * Return the contents of the session in array form. + */ + public function contents() { + return Session::getAll(); + } +} + + +?> \ No newline at end of file diff --git a/security/OpenIDLoginForm.php b/security/OpenIDLoginForm.php new file mode 100644 index 000000000..90fd3f45d --- /dev/null +++ b/security/OpenIDLoginForm.php @@ -0,0 +1,140 @@ + + */ +class OpenIDLoginForm extends Form { + + /** + * Constructor + * + * @param $controller + * @param $name + * @param $fields + * @param $actions + * @param $checkCurrentUser + */ + function __construct($controller, $name, $fields = null, $actions = null, + $checkCurrentUser = true) { + $customCSS = project() . '/css/openid_login.css'; + if(Director::fileExists($customCSS)) { + Requirements::css($customCSS); + } + + if(isset($_REQUEST['BackURL'])) { + $backURL = $_REQUEST['BackURL']; + } else { + $backURL = Session::get('BackURL'); + //Session::clear("BackURL"); don't clear the back URL here! Should be used until the right password is entered! + } + + if($checkCurrentUser && Member::currentUserID()) { + $fields = new FieldSet(); + $actions = new FieldSet(new FormAction("logout", "Log in as someone else")); + } else { + if(!$fields) { + $fields = new FieldSet( + new HiddenField("AuthenticationMethod", null, "OpenID"), + new TextField("OpenIDURL", "OpenID URL", + Session::get('SessionForms.OpenIDLoginForm.OpenIDURL')), + new CheckboxField("Remember", "Remember me next time?", true) + ); + } + if(!$actions) { + $actions = new FieldSet( + new FormAction("dologin", "Log in") + ); + } + } + + if(isset($backURL)) { + $fields->push(new HiddenField('BackURL', 'BackURL', $backURL)); + } + + parent::__construct($controller, $name, $fields, $actions); + } + + + /** + * Get message from session + */ + protected function getMessageFromSession() { + parent::getMessageFromSession(); + if(($member = Member::currentUser()) && + !Session::get('OpenIDLoginForm.force_message')) { + $this->message = "You're logged in as $member->FirstName."; + } + Session::set('OpenIDLoginForm.force_message', false); + } + + + /** + * Login form handler method + * + * This method is called when the user clicks on "Log in" + * + * @param array $data Submitted data + */ + public function dologin($data) { + if($this->performLogin($data)){ + + if($backURL = $_REQUEST['BackURL']) { + Session::clear("BackURL"); + Session::clear('SessionForms.OpenIDLoginForm.OpenIDURL'); + Director::redirect($backURL); + } else + Director::redirectBack(); + + } else { + Session::set('SessionForms.OpenIDLoginForm.OpenIDURL', $data['OpenIDURL']); + if($badLoginURL = Session::get("BadLoginURL")){ + Director::redirect($badLoginURL); + } else { + Director::redirectBack(); + } + } + } + + + /** + * Log out + * + * @todo Figure out for what this method is used! Is it really used at all? + */ + public function logout() { + $s = new Security(); + return $s->logout(); + } + + + /** + * Try to authenticate the user + * + * @param array Submitted data + * @return Member Returns the member object on successful authentication + * or NULL on failure. + */ + public function performLogin(array $data) { + if($member = OpenIDAuthenticator::authenticate($data)) { + $firstname = Convert::raw2xml($member->FirstName); + $this->sessionMessage("Welcome Back, {$firstname}", "good"); + $member->LogIn(); + + if(isset($data['Remember'])) { + // Deliberately obscure... + Cookie::set('alc_enc',base64_encode("$data[OpenIDURL]")); + } + return $member; + + } else { + $this->sessionMessage("Login failed. Please try again.", "bad"); + return null; + } + } +} + + +?> \ No newline at end of file diff --git a/security/Security.php b/security/Security.php index a264a429a..03296ecf4 100644 --- a/security/Security.php +++ b/security/Security.php @@ -29,7 +29,7 @@ class Security extends Controller { * If you don't provide a messageSet, a default will be used. */ static function permissionFailure($page = null, $messageSet = null) { - $loginForm = singleton('Security')->LoginForm(); +// $loginForm = singleton('Security')->LoginForm(); //user_error('debug', E_USER_ERROR); // Prepare the messageSet provided @@ -60,7 +60,9 @@ class Security extends Controller { $message = $messageSet['default']; } - $loginForm->sessionMessage($message, 'warning'); +/* $loginForm->sessionMessage($message, 'warning'); + Session::set("SecurityMessage.Error.message", $message); + Session::set("SecurityMessage.Error.type", $type);*/ // $_SESSION['LoginForm']['message'] = $message; Session::set("BackURL", $_SERVER['REQUEST_URI']); @@ -73,15 +75,59 @@ class Security extends Controller { return; } + + /** + * Get the login form to process according to the submitted data + */ function LoginForm() { - return MemberAuthenticator::GetLoginForm(); + if(is_array($_REQUEST) && isset($_REQUEST['AuthenticationMethod'])) + { + switch($_REQUEST['AuthenticationMethod']) + { + case 'Member': + return MemberAuthenticator::GetLoginForm(); + break; + case 'OpenID': + return OpenIDAuthenticator::GetLoginForm(); + break; + } + } + user_error('Invalid authentication method', E_USER_ERROR); } + + + /** + * Get the login forms for all available authentication methods + * + * @todo Check how to activate/deactivate authentication methods + */ + function GetLoginForms() + { + $forms = array(); + array_push($forms, MemberAuthenticator::GetLoginForm()); + array_push($forms, OpenIDAuthenticator::GetLoginForm()); + + return $forms; + } + + + /** + * Get a link to a security action + * + * @param string $action Name of the action + */ function Link($action = null) { return "Security/$action"; } + + /** + * Log the currently logged in user out + * * @param bool $redirect Redirect the user back to where they came. - * - If it's false, the code calling logout() is responsible for sending the user where-ever they should go. + * - If it's false, the code calling logout() is + * responsible for sending the user where-ever + * they should go. */ function logout($redirect = true) { Cookie::set('alc_enc',null); @@ -90,6 +136,10 @@ class Security extends Controller { if($redirect) Director::redirectBack(); } + + /** + * + */ function login() { Requirements::javascript("jsparty/behaviour.js"); Requirements::javascript("jsparty/loader.js"); @@ -109,14 +159,23 @@ class Security extends Controller { return $controller->renderWith(array("Security_login", "Page")); } else { + $forms = $this->GetLoginForms(); + $content = ''; + foreach($forms as $form) + $content .= $form->forTemplate(); + $customisedController = $controller->customise(array( - "Content" => $this->LoginForm()->forTemplate() + "Content" => $content )); return $customisedController->renderWith("Page"); } } + + /** + * + */ function lostpassword() { Requirements::javascript("jsparty/prototype.js"); Requirements::javascript("jsparty/behaviour.js"); @@ -138,6 +197,10 @@ class Security extends Controller { return $customisedController->renderWith("Page"); } + + /** + * + */ function passwordsent() { Requirements::javascript("jsparty/behaviour.js"); Requirements::javascript("jsparty/loader.js"); @@ -161,6 +224,9 @@ class Security extends Controller { } + /** + * + */ function LostPasswordForm() { return new MemberLoginForm($this, "LostPasswordForm", new FieldSet( new EmailField("Email", "Email address") @@ -188,6 +254,7 @@ class Security extends Controller { return $member; } + /** * Return a member with administrator privileges */ @@ -223,6 +290,7 @@ class Security extends Controller { return $member; } + /** * This will set a static default-admin (e.g. "td") which is not existing as * a database-record. By this workaround we can test pages in dev-mode with @@ -240,6 +308,7 @@ class Security extends Controller { self::$password = $password; } + /** * Set strict path checking. This prevents sharing of the session * across several sites in the domain. @@ -250,8 +319,13 @@ class Security extends Controller { self::$strictPathChecking = $strictPathChecking; } + + /** + * + */ static function getStrictPathChecking() { return self::$strictPathChecking; } } -?> + +?> \ No newline at end of file