From 20f26660c5bf5bda0c032cade2932904de39adc0 Mon Sep 17 00:00:00 2001 From: Nic Waller Date: Tue, 28 May 2013 23:01:26 -0700 Subject: [PATCH] Import SVN HEAD from Google Code https://code.google.com/p/yourls-authmgr-plugin/source/browse/?r=12 --- plugin.php | 364 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 plugin.php diff --git a/plugin.php b/plugin.php new file mode 100644 index 0000000..1913428 --- /dev/null +++ b/plugin.php @@ -0,0 +1,364 @@ + AuthmgrCapability::AddURL, + 'delete' => AuthmgrCapability::DeleteURL, + 'edit_display' => AuthmgrCapability::EditURL, + 'edit_save' => AuthmgrCapability::EditURL, + 'activate' => AuthmgrCapability::ManagePlugins, + 'deactivate' => AuthmgrCapability::ManagePlugins, + ); + + // intercept requests for plugin management + if ( isset( $_REQUEST['plugin'] ) ) { + $action_keyword = $_REQUEST['action']; + $cap_needed = $action_capability_map[$action_keyword]; + if ( $cap_needed !== NULL && authmgr_have_capability( $cap_needed ) !== true) { + yourls_redirect( yourls_admin_url( '?access=denied' ), 302 ); + } + } + + // also intercept AJAX requests + if ( yourls_is_Ajax() ) { + $action_keyword = $_REQUEST['action']; + $cap_needed = $action_capability_map[$action_keyword]; + if ( authmgr_have_capability( $cap_needed ) !== true) { + $err = array(); + $err['status'] = 'fail'; + $err['code'] = 'error:authorization'; + $err['message'] = 'Access Denied'; + $err['errorCode'] = '403'; + echo json_encode( $err ); + die(); + } + } +} + +yourls_add_filter( 'logout_link', 'authmgr_html_append_roles' ); +function authmgr_html_append_roles( $original ) { + $authenticated = yourls_is_valid_user(); + if ( $authenticated === true ) { + $listcaps = implode(', ', authmgr_enumerate_current_capabilities()); + return '
'.$original.'
'; + } else { + return $original; + } +} + +/**************** CAPABILITY TEST/ENUMERATION ****************/ + +/* + * If capability is not permitted in current context, then abort. + * This is the most basic way to intercept unauthorized usage. + */ +function authmgr_require_capability( $capability ) { + if ( !authmgr_have_capability( $capability ) ) { + // TODO: display a much nicer error page + //die('Sorry, you are not authorized for the action: '.$capability); + yourls_redirect( yourls_admin_url( '?access=denied' ), 302 ); + die(); + } +} + +/* + * Returns array of capabilities currently available. + */ +function authmgr_enumerate_current_capabilities() { + $current_capabilities = array(); + $all_capabilities = authmgr_enumerate_all_capabilities(); + + foreach ( $all_capabilities as $cap ) { + if ( authmgr_have_capability( $cap ) ) { + $current_capabilities[] = $cap; + } + } + + return $current_capabilities; +} + +function authmgr_enumerate_all_capabilities() { + // TODO: generalize this, instead of just repeating the total declaration + return array( + AuthmgrCapability::ShowAdmin, + AuthmgrCapability::AddURL, + AuthmgrCapability::DeleteURL, + AuthmgrCapability::EditURL, + AuthmgrCapability::ManagePlugins, + AuthmgrCapability::API, + AuthmgrCapability::ViewStats, + ); +} + +/* + * Is the requested capability permitted in this context? + */ +function authmgr_have_capability( $capability ) { + return yourls_apply_filter( AUTHMGR_ALLOW, false, $capability); +} + +/******************* FILTERS THAT GRANT CAPABILITIES *****************************/ +/* By filtering AUTHMGR_ALLOW, you can grant capabilities without using roles. */ +/*********************************************************************************/ + +/* + * What capabilities are always available, including anonymous users? + */ +yourls_add_filter( AUTHMGR_ALLOW, 'authmgr_check_anon_capability', 5 ); +function authmgr_check_anon_capability( $original, $capability ) { + global $authmgr_anon_capabilities; + + // Shortcut - trust approval given by earlier filters + if ( $original === true ) return true; + + // Make sure the anon rights list has been setup + authmgr_environment_check(); + + // Check list of capabilities that don't require authentication + return in_array( $capability, $authmgr_anon_capabilities ); +} + +/* + * What capabilities are available through role assignments to the active user? + */ +yourls_add_filter( AUTHMGR_ALLOW, 'authmgr_check_user_capability', 10 ); +function authmgr_check_user_capability( $original, $capability ) { + global $authmgr_role_capabilities; + + // Shortcut - trust approval given by earlier filters + if ( $original === true ) return true; + + // ensure $authmgr_role_capabilities has been set up + authmgr_environment_check(); + + // If the user is not authenticated, then give up because only users have roles. + $authenticated = yourls_is_valid_user(); + if ( $authenticated !== true ) + return false; + + // Enumerate the capabilities available to this user through roles + $user_caps = array(); + + foreach ( $authmgr_role_capabilities as $rolename => $rolecaps ) { + if ( authmgr_user_has_role( YOURLS_USER, $rolename ) ) { + $user_caps = array_merge( $user_caps, $rolecaps ); + } + } + $user_caps = array_unique( $user_caps ); + + // Is the desired capability in the enumerated list of capabilities? + return in_array( $capability, $user_caps ); +} + +/* + * If the user is connecting from an IP address designated for admins, + * then all capabilities are automatically granted. + * + * By default, only 127.0.0.0/8 (localhost) is an admin range. + */ +yourls_add_filter( AUTHMGR_ALLOW, 'authmgr_check_admin_ipranges', 15 ); +function authmgr_check_admin_ipranges( $original, $capability ) { + global $authmgr_admin_ipranges; + + // Shortcut - trust approval given by earlier filters + if ( $original === true ) return true; + + // ensure $authmgr_admin_ipranges is setup + authmgr_environment_check(); + + foreach ($authmgr_admin_ipranges as $range) { + if ( authmgr_cidr_match( $_SERVER['REMOTE_ADDR'], $range ) ) + return true; + } + + return $original; // effectively returns false +} + +/* + * What capabilities are available when making API requests without a username? + */ +yourls_add_filter( AUTHMGR_ALLOW, 'authmgr_check_apiuser_capability', 15 ); +function authmgr_check_apiuser_capability( $original, $capability ) { + // Shortcut - trust approval given by earlier filters + if ( $original === true ) return true; + + // In API mode and not using user/path authn? Let it go. + if ( yourls_is_API() && !isset($_REQUEST['username']) ) + return true; + // TODO: add controls for actions, like + // shorturl, stats, db-stats, url-stats, expand + + return $original; +} + +/******************** ROLE TEST AND ENUMERATION ***********************/ + +/* + * Determine whether a specific user has a role. + */ +function authmgr_user_has_role( $username, $rolename ) { + return yourls_apply_filter( AUTHMGR_HASROLE, false, $username, $rolename ); +} + +// ******************* FILTERS THAT GRANT ROLE MEMBERSHIP ********************* +// By filtering AUTHMGR_HASROLE, you can connect internal roles to something else. +// Any filter handlers should execute as quickly as possible. + +/* + * What role memberships are defined for the user in user/config.php? + */ +yourls_add_filter( AUTHMGR_HASROLE, 'authmgr_user_has_role_in_config'); +function authmgr_user_has_role_in_config( $original, $username, $rolename ) { + global $authmgr_role_assignment; + + // if no role assignments are created, grant everything + // so the site still works even if stuff is configured wrong + if ( empty( $authmgr_role_assignment ) ) + return true; + + // do this the case-insensitive way + // the entire array was made lowercase in environment check + $username = strtolower($username); + $rolename = strtolower($rolename); + + // if the role doesn't exist, give up now. + if ( !in_array( $rolename, array_keys( $authmgr_role_assignment ) ) ) + return false; + + $users_in_role = $authmgr_role_assignment[$rolename]; + return in_array( $username, $users_in_role ); +} + + +/********************* VALIDATE CONFIGURATION ************************/ + +function authmgr_environment_check() { + global $authmgr_anon_capabilities; + global $authmgr_role_capabilities; + global $authmgr_role_assignment; + + if ( !isset( $authmgr_anon_capabilities) ) { + $authmgr_anon_capabilities = array( + AuthmgrCapability::API, + AuthmgrCapability::ShowAdmin,//TODO: hack! how to allow logon page? + ); + } + + if ( !isset( $authmgr_role_capabilities) ) { + $authmgr_role_capabilities = array( + AuthmgrRoles::Administrator => array( + AuthmgrCapability::ShowAdmin, + AuthmgrCapability::AddURL, + AuthmgrCapability::DeleteURL, + AuthmgrCapability::EditURL, + AuthmgrCapability::ManagePlugins, + AuthmgrCapability::API, + AuthmgrCapability::ViewStats, + ), + AuthmgrRoles::Editor => array( + AuthmgrCapability::ShowAdmin, + AuthmgrCapability::AddURL, + AuthmgrCapability::EditURL, + AuthmgrCapability::DeleteURL, + AuthmgrCapability::ViewStats, + ), + AuthmgrRoles::Contributor => array( + AuthmgrCapability::ShowAdmin, + AuthmgrCapability::AddURL, + AuthmgrCapability::ViewStats, + ), + ); + } + + if ( !isset( $authmgr_role_assignment ) ) { + $authmgr_role_assignment = array(); + } + + if ( !isset( $authmgr_iprange_roles ) ) { + $authmgr_admin_ipranges = array( + '127.0.0.0/8', + ); + } + + // convert role assignment table to lower case if it hasn't been done already + // this makes searches much easier! + // TODO: avoid doing this every time we validate + $authmgr_role_assignment_lower = array(); + foreach ( $authmgr_role_assignment as $key => $value ) { + $t_key = strtolower( $key ); + $t_value = array_map('strtolower', $value); + $authmgr_role_assignment_lower[$t_key] = $t_value; + } + $authmgr_role_assignment = $authmgr_role_assignment_lower; + unset($authmgr_role_assignment_lower); + + return true; +} + +// ***************** GENERAL UTILITY FUNCTIONS ******************** + +/* + * Borrowed from: + * http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5 + */ +function authmgr_cidr_match($ip, $range) +{ + list ($subnet, $bits) = split('/', $range); + $ip = ip2long($ip); + $subnet = ip2long($subnet); + $mask = -1 << (32 - $bits); + $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned + return ($ip & $mask) == $subnet; +} \ No newline at end of file