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