diff --git a/SampleAuth.php b/SampleAuth.php index 39f0e5b..d8c3411 100644 --- a/SampleAuth.php +++ b/SampleAuth.php @@ -15,9 +15,9 @@ function register() { $this->description = plugin_lang_get( 'description' ); $this->page = ''; - $this->version = '0.1'; + $this->version = '0.2'; $this->requires = array( - 'MantisCore' => '2.3.0-dev', + 'MantisCore' => '2.14.0-dev', ); $this->author = 'MantisBT Team'; @@ -37,16 +37,23 @@ function hooks() { return $t_hooks; } + function config() { + return array( + # set to 'true', if the plugin can do autoprovisioning - otherwise only "known" users will be able to log in + 'autoprovision' => true, + # sets the access level configured by autoprov; defaults to system configures default access level + 'default_access_level' => config_get( 'default_new_account_access_level' ) + ); + } + function auth_user_flags( $p_event_name, $p_args ) { # Don't access DB if db_is_connected() is false. - $t_username = $p_args['username']; $t_user_id = $p_args['user_id']; - # If user is unknown, don't handle authentication for it, since this plugin doesn't do - # auto-provisioning - if( !$t_user_id ) { + # If user is unknown and autoprovision is not set, than don't handle authentication for it + if( !$t_user_id && ! plugin_config_get( 'autoprovision' ) ) { return null; } @@ -55,14 +62,29 @@ function auth_user_flags( $p_event_name, $p_args ) { return null; } - $t_access_level = user_get_access_level( $t_user_id, ALL_PROJECTS ); + if( $t_user_id ) { + $t_access_level = user_get_access_level( $t_user_id, ALL_PROJECTS ); - # Have administrators use default login flow - if( $t_access_level >= ADMINISTRATOR ) { + # Have administrators use default login flow + if( $t_access_level >= ADMINISTRATOR ) { return null; + } } - # for everybody else use the custom authentication + /* + * + * add any filter parameters here + * + * e.g. if you want the plugin to handle usernames only which contain '@': + * + * if ( ! preg_match('/^.*@.*$/',$t_username) ) { + * return null; + * } + * + * or to use this custom authenticateion for everybody else: + * + */ + $t_flags = new AuthFlags(); # Passwords managed externally for all users @@ -71,9 +93,28 @@ function auth_user_flags( $p_event_name, $p_args ) { # No one can use standard auth mechanism - # Override Login page and Logout Redirect - $t_flags->setCredentialsPage( helper_url_combine( plugin_page( 'login', /* redirect */ true ), 'username=' . $t_username ) ); - $t_flags->setLogoutRedirectPage( plugin_page( 'logout', /* redirect */ true ) ); + # Override Credentials, Authenticator page and Logout Redirect - see 'pages' subdirectory + /* + * + * custom Credentials Page for user. This is displayed after the user did input his username (and the username is known to Mantis or plugin is autoprov capable) + * + */ + //$t_flags->setCredentialsPage( helper_url_combine( plugin_page( 'credentials', /* redirect */ true ), 'username=' . $t_username ) ); + /* + * + * custom Authenticator Page for user. This is called, when the user entered both username and password in the standard MantisBT login flow + * username and password in $_POST + * + * Please NOTE: if you don't do any filtering - e.g. e-mail - than this will be the only Auth Plugin besides the built-in! Stacking is not (yet) supported + * + */ + $t_flags->setAuthenticatorPage( helper_url_combine( plugin_page( 'login', /* redirect */ true ), ( !empty($t_username) ? 'username=' . urlencode($t_username) : '' ) ) ); + /* + * + * custom Logout Page for user. + * + */ + //$t_flags->setLogoutRedirectPage( plugin_page( 'logout', /* redirect */ true ) ); # No long term session for identity provider to be able to kick users out. $t_flags->setPermSessionEnabled( false ); diff --git a/core/CustomAuthPlugin.php b/core/CustomAuthPlugin.php new file mode 100644 index 0000000..820fa25 --- /dev/null +++ b/core/CustomAuthPlugin.php @@ -0,0 +1,183 @@ +. + * + * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net + */ + +/** + * Class for dealing with custom authentication requests + * + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * Class is written for demonstration purposes ONLY!!! + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * @copyright Tamas Dajka 2018 + * @author Tamas Dajka + * @link http://www.mantisbt.org + * @package MantisBT + * @subpackage classes + * @plugin SampleAuth + */ + +/* +* +* User API is needed for user auto provision +* +*/ +require_api( 'user_api.php' ); +require_api( 'email_api.php' ); + +class CustomAuthPlugin { + + /** + * Constructor + */ + function __construct() { + # spaceholder + } + + /** + * login + * + * @params: username, password + * @return: + * - false on login failure + * - username on login success + */ + function login( $username, $password ) { + /* + * + * Check access/auth in remote system + * + */ + if ( !$this->auth($username,$password) ) { + return false; + } + + /* + * + * Should we autoprovision the user? + * + */ + if( plugin_config_get( 'autoprovision' ) ) { + /* + * Check if user exists, probably needs customization + */ + if( ! user_get_id_by_name($username) ) { + /* + * data needed for autorpovision: + * - username + * - password + * + * Optional params: + * + * - email + * - access_level (null) + * - protected (false) + * - enabled (true) + * - realname + * - admin_name + * + */ + + $t_user_data = $this->get_user_data( $username ); + + /* + * create user, but with empty e-mail => prevent mantis from sending signup e-mail + * we set a strong random password + * + * To get this work, you either have to set $g_allow_blank_email = ON, or change the value here on-the-fly (don't forget to set it back) + * + */ + $original_g_allow_blank_email = config_get( 'allow_blank_email' ); + config_set_global( 'allow_blank_email', ON ); + user_create( $username, auth_generate_random_password(24), '', $t_user_data['access_level'], false, true, $t_user_data['realname'] ); + config_set_global( 'allow_blank_email', $original_g_allow_blank_email ); + + /* + * Set user e-mail + */ + if( !is_blank( $t_user_data['email'] ) && email_is_valid( $t_user_data['email'] ) && ( $t_user_id = user_get_id_by_name( $username ) ) ) { + user_set_field( $t_user_id,'email', $t_user_data['email'] ); + } + } + } + + return $username; + } + + /** + * logout + * + * @return: bool + */ + function logout() { + # spaceholder + return true; + } + + /** + * collects user data from auth system + * + * @return: array() + */ + function get_user_data( $username = '' ) { + if ( empty($username) ) { + return array(); + } + + /* + * dummy data for now, access_level 25 is REPORTER -> see /core/constant_inc.php + */ + return array( 'username' => $username, 'email' => 'john.doe@gmail.com', 'access_level' => config_get( 'default_new_account_access_level' ), 'realname' => 'John Doe' ); + } + + /** + * Auth validity check + * + * @params: username, password + * @return: bool + */ + function auth( $username, $password ) { + if ( empty( $username ) || empty( $password ) ) { + return false; + } + + /* + * We should check for external auth here + * + * dummy return for now + */ + + # comment this out for testing or write your own + return false; + + /* example authentication */ + if( $username == 'john.doe' && $password == 'Abc.123' ) { + return true; + } else { + return false; + } + } +} diff --git a/pages/credentials.php b/pages/credentials.php new file mode 100644 index 0000000..3c17cd1 --- /dev/null +++ b/pages/credentials.php @@ -0,0 +1,56 @@ + 1, + 'username' => $f_username, + ); + + if( !is_blank( 'return' ) ) { + $t_query_args['return'] = $t_return; + } + + if( $f_reauthenticate ) { + $t_query_args['reauthenticate'] = 1; + } + + $t_query_text = http_build_query( $t_query_args, '', '&' ); + // we will create a loop this way - this will redirect us again to this login page... + //$t_uri = auth_login_page( $t_query_text ); + // no "stack like" auth mechs, forcing default page on error + $t_uri = helper_url_combine( AUTH_PAGE_USERNAME, $t_query_args); + + print_header_redirect( $t_uri ); +} + +# Let user into MantisBT +auth_login_user( $t_user_id ); + +# Redirect to original page user wanted to access before authentication +if( !is_blank( $t_return ) ) { + print_header_redirect( 'login_cookie_test.php?return=' . $t_return ); +} + +# If no return page, redirect to default page +print_header_redirect( config_get( 'default_home_page' ) ); diff --git a/pages/login.php b/pages/login.php index 3bb29a7..559ec94 100644 --- a/pages/login.php +++ b/pages/login.php @@ -6,13 +6,36 @@ require_api( 'authentication_api.php' ); require_api( 'user_api.php' ); -$f_username = gpc_get( 'username' ); +$f_username = gpc_get_string( 'username', '' ); +$f_password = gpc_get_string( 'password', '' ); $f_reauthenticate = gpc_get_bool( 'reauthenticate', false ); $f_return = gpc_get_string( 'return', config_get( 'default_home_page' ) ); $t_return = string_url( string_sanitize_url( $f_return ) ); -# TODO: use custom authentication method here. +$f_username = auth_prepare_username( $f_username ); +$f_password = auth_prepare_password( $f_password ); + +/* +* +* Log in the user with the custom class +* +* class should return username/false upon successful/failed login +* +*/ + +plugin_require_api( 'core/CustomAuthPlugin.php' ); +$cap = new CustomAuthPlugin(); +if ( ( $f_username = $cap->login($f_username,$f_password) ) ) { + /* + * + * All set, good to go + * + * if you want to assign the user to project(s) based on a criteria + * than this is the right place. Don't forget to check if it's already assigned + * + */ +} $t_user_id = is_blank( $f_username ) ? false : user_get_id_by_name( $f_username ); @@ -31,8 +54,10 @@ } $t_query_text = http_build_query( $t_query_args, '', '&' ); - - $t_uri = auth_login_page( $t_query_text ); + // we will create a loop this way - this will redirect us again to this login page... + //$t_uri = auth_login_page( $t_query_text ); + // no "stack like" auth mechs, forcing default page on error + $t_uri = helper_url_combine( AUTH_PAGE_USERNAME, $t_query_args); print_header_redirect( $t_uri ); } diff --git a/pages/logout.php b/pages/logout.php index 1b9d0c4..1d18f70 100644 --- a/pages/logout.php +++ b/pages/logout.php @@ -6,6 +6,15 @@ require_api( 'authentication_api.php' ); # User is already logged out from Mantis -# TODO: logout from external identity provider +# TODO by the plugin: logout from external identity provider if necessary or redirect to custom page +/** +* +* plugin_require_api( 'core/CustomAuthPlugin.php' ); +* $cap = new CustomAuthPlugin(); +* $cap->logout(); +* +*/ + +# default redirect to Mantis login page print_header_redirect( auth_login_page(), true, false ); diff --git a/readme.md b/readme.md index f3f5efa..5818ffc 100644 --- a/readme.md +++ b/readme.md @@ -2,23 +2,30 @@ This is a sample authentication plugin showing how a MantisBT authentication plugin can implement its own authentication and control authentication related flags on a per user basis. +Autoprov and authenticator support added by Tamas Dajka (viper@vipernet.hu) + The authentication mechanism implemented by this plugin works as follows: - If user is administrator, use standard authentication. -- If user is not registered in the db, user standard behavior. +- If user is not registered in the db, use standard behavior; or if autoprovision is set then provision the user - Otherwise, auto-signin the user without a password. Users that are auto-signed in, can't manage or use passwords that are stored in the MantisBT database. The plugin can be easily modified to redirect to an identity provider and validate the token returned or validate a username and password against a database or LDAP. +## Config parameters +- `autoprovision` set to 'true', if the plugin can do autoprovisioning - otherwise only "known" users will be able to log in +- `default_access_level` sets the access level configured by autoprov; defaults to system configures default access level + ## Authentication Flags The authentication flags events enables the plugin to control MantisBT core authentication behavior on a per user basis. Plugins can also show their own pages to accept credentials from the user. - `password_managed_elsewhere_message` message to show in MantisBT UI to indicate that password is managed externally. If left blank or not set, the default message will be used. - `can_use_standard_login` true then standard password form and validation is used, false: otherwise. -- `login_page` Custom login page to use. -- `credential_apge` The page to show to ask the user for their credential. +- `login_page` Custom login page to use. Will be called, if Mantis fails to authenticate the user against it's own mechanisms +- `credential_page` The page to show to ask the user for their credential. +- `authenticator_page` The page to validate the username AND password - `logout_page` Custom logout page to use. - `logout_redirect_page` Page to redirect to after user is logged out. - `session_lifetime` Default session lifetime in seconds or 0 for browser session. @@ -36,6 +43,37 @@ the user typed in the first login page that asks for username. If plugin doesn't want to handle a specific user, it should return null. Otherwise, it should return the `AuthFlags` with the overriden settings. +## Options/Setup possibilities + +If you want to use multiple auth backends, than you'll have to do filtering. You'll have two possibilities to do so: +- use authenticator_page and setup the login there +- setup filtering in class and have multiple instances of the class (not yet tested) + +1. Set login_page + - if you set the login page, than the user will be validated after Mantis runs login.php. It's difficult to properly set up, due to Mantis logic + - only works, if the user is known to Mantis. Autoprovisioning is not possible. + +2. Set credential_page + - you'll be able to use SSO or orher 3rd party auth provider + - this is called, once Mantis asks for the username + - you'll have to ask for the user's password + - will only work, if the user is known to Mantis OR you set up and turn on autoprovisioning! + +3. Set authenticator_page + - you'll make use of Mantis built in login mech (username/password page) + - you'll have to use your own method to authenticate the user, with the possibility of autoprovision it (and assign to projects, etc) + + +## About Mantis login flow + +The following describes the standard login flow of Mantis; if user is not logged in, then login_page is shown to aquire the username. + +- Login Page to aquire username +- Username is sent to Login Password page + - if CredentialsPage authflag is set, then user is redirected to it _(please note, user must be known to Mantis or user autoprovision must be configured and enabled)_ + - if AuthenticatorPage authflag is set then the password is aquired, but all data is POST-ed to the page provided +- Username and Password is POST-ed to login.php, which validates the data. If you set LoginPage authflag, than the user will be redirected to it, if her/his credentials (password) were not valid _(NOTE that user must be known to Mantis for this to work!)_ + ## Screenshots Native Login Page for Username @@ -51,4 +89,4 @@ User My Account Page ![Profile Page](doc/sample_auth_no_password_change.png "Profile Page") ## Dependencies -MantisBT v2.3.0-dev once auth plugin support is added. +MantisBT v2.14.0-dev once authenticator and autoprovision support is added.