Changeset 2310981
- Timestamp:
- 05/24/2020 07:30:32 AM (6 years ago)
- Location:
- xlogin
- Files:
-
- 2 added
- 12 edited
-
assets/screenshot-3.png (modified) (previous)
-
assets/screenshot-4.png (modified) (previous)
-
trunk/html/admin/customize.html (added)
-
trunk/html/admin/customize.js (added)
-
trunk/html/admin/xsvcs.html (modified) (1 diff)
-
trunk/html/admin/xsvcs.js (modified) (3 diffs)
-
trunk/html/admin/xusers.js (modified) (1 diff)
-
trunk/includes/admin.php (modified) (1 diff)
-
trunk/includes/api.php (modified) (1 diff)
-
trunk/includes/login.php (modified) (2 diffs)
-
trunk/init.php (modified) (1 diff)
-
trunk/lib/XLogin.php (modified) (15 diffs)
-
trunk/lib/XLoginApi.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
xlogin/trunk/html/admin/xsvcs.html
r2265498 r2310981 41 41 No import from external profile. 42 42 </span> 43 </td> 44 </tr> 45 <tr> 46 <td>Guest user:</td> 47 <td> 48 <input type="text" v-model="svc.data.guest" 49 placeholder="Guest user for unknown aliases" 50 > 51 <button type="button" 52 @click="checkGuestUser()" 53 :disabled="guestUserNotSpecified" 54 >Check</button> 43 55 </td> 44 56 </tr> -
xlogin/trunk/html/admin/xsvcs.js
r2265498 r2310981 17 17 xsvcs: [] 18 18 }, 19 created() { 20 window.addEventListener('keyup', e => { 21 if (!this.svc) 22 return; 23 if (e.key == 'Escape') { 24 this.svc = null; 25 } 26 }); 27 }, 19 28 mounted() { 20 29 pl2010_XLoginApi.get('/xsvcs').done(resp => { … … 29 38 }, 30 39 computed: { 40 guestUserNotSpecified: function() { 41 let xs = this.svc; 42 if (!xs || !xs.data || !xs.data.guest) 43 return true; 44 if (xs.data.guest.trim().length == 0) 45 return true; 46 return null; 47 }, 31 48 incompleteSvcConfig: function() { 32 49 let xs = this.svc; … … 60 77 }, 61 78 methods: { 79 /** 80 * Check if a guest user is acceptable. 81 */ 82 checkGuestUser() { 83 if (this.guestUserNotSpecified) 84 return; 85 let guest = this.svc.data.guest.trim(); 86 this.svc.data.guest = guest; 87 pl2010_XLoginApi.post('/admin', { 88 op: 'check-guest', 89 params: { 90 login: guest 91 } 92 }).done(resp => { 93 if (this.guestUserNotSpecified) 94 return; 95 if (this.svc.data.guest != guest) 96 return; 97 let result = resp.result || {}; 98 if (result.success) { 99 if (result.login) 100 this.svc.data.guest = result.login; 101 alert('Guest user acceptable.'); 102 } 103 else { 104 alert('Guest user reject: ' + result.err_msg); 105 } 106 }); 107 }, 108 62 109 /** 63 110 * Configure external login service. -
xlogin/trunk/html/admin/xusers.js
r2264830 r2310981 46 46 }, 47 47 xusers: [] 48 }, 49 created() { 50 window.addEventListener('keyup', e => { 51 if (!this.modal.add && !this.modal.upload) 52 return; 53 if (e.key == 'Escape') { 54 this.modal.add = false; 55 this.modal.upload = false; 56 } 57 }); 48 58 }, 49 59 mounted() { -
xlogin/trunk/includes/admin.php
r2264830 r2310981 75 75 // }}} 76 76 //------------------------------------------------------ 77 // Customization section. {{{ 78 // 79 add_settings_section( 80 'pl2010-xlogin-customize', 81 __('Customization', 'pl2010'), 82 function($args) { 83 $admin = __DIR__."/../html/admin"; 84 echo file_get_contents("{$admin}/customize.html"); 85 ?> 86 <script type="text/javascript"> 87 <?php 88 echo file_get_contents("{$admin}/customize.js"); 89 ?> 90 </script> 91 <?php 92 }, 93 'pl2010-xlogin' 94 ); 95 // }}} 96 //------------------------------------------------------ 77 97 } /*}}}*/); 78 98 -
xlogin/trunk/includes/api.php
r2264830 r2310981 15 15 $namespace = 'pl2010/xlogin/v1'; 16 16 17 //-------------------------------------------------------------- 18 // Retrieve customization configuration. {{{ 19 // 20 register_rest_route($namespace, '/customize', [ 21 'methods' => 'GET', 22 'callback' => function($req) use($xlogin) { 23 $conf = $xlogin->getCustomization(); 24 return XLoginApi::checkError($conf) ?? [ 25 'data' => $conf, 26 ]; 27 }, 28 'permission_callback' => 'PL2010\WordPress\XLoginApi::checkPermRead', 29 ]); 30 // }}} 31 //-------------------------------------------------------------- 32 // Update customization configuration. {{{ 33 // 34 register_rest_route($namespace, '/customize', [ 35 'methods' => 'POST', 36 'callback' => function($req) use($xlogin) { 37 $conf = $req->get_param('data'); 38 $conf = $xlogin->updateCustomization($conf); 39 return XLoginApi::checkError($conf) ?? [ 40 'data' => $conf, 41 ]; 42 }, 43 'args' => [ 44 'data' => [ 45 'required' => true, 46 'validate_callback' => function($data) { 47 return is_array($data); 48 }, 49 ], 50 ], 51 'permission_callback' => 'PL2010\WordPress\XLoginApi::checkPermUpdate', 52 ]); 53 // }}} 54 //-------------------------------------------------------------- 55 // Miscellaneous admin API. {{{ 56 // 57 register_rest_route($namespace, '/admin', [ 58 'methods' => 'POST', 59 'callback' => function($req) use($xapi) { 60 $op = $req->get_param('op'); 61 $params = $req->get_param('params'); 62 $result = $xapi->performAdminOp($op, $params); 63 return XLoginApi::checkError($result) ?? [ 64 'result' => $result, 65 ]; 66 }, 67 'args' => [ 68 'op' => [ 69 'required' => true, 70 'validate_callback' => function($op) { 71 return is_string($op); 72 }, 73 ], 74 'params' => [ 75 'required' => false, 76 'validate_callback' => function($params) { 77 return is_array($params); 78 }, 79 ], 80 ], 81 'permission_callback' => 'PL2010\WordPress\XLoginApi::checkPermAdmin', 82 ]); 83 // }}} 17 84 //-------------------------------------------------------------- 18 85 // Get list of external services. {{{ -
xlogin/trunk/includes/login.php
r2264830 r2310981 84 84 filemtime("{$pluginDir}css/login.css") 85 85 ); 86 87 $customize = $xlogin->getCustomization(); 86 88 ?> 87 89 <div class="pl2010-xlogin-launch"> … … 133 135 <?php 134 136 } 137 if ($customize['login_buttons_info'] ?? null) { 138 ?><p class="description"><?php 139 esc_html_e($customize['login_buttons_info']); 140 ?></p><?php 141 } 135 142 ?> 136 143 </div> -
xlogin/trunk/init.php
r2264830 r2310981 107 107 return $user; 108 108 109 if ($auth != '') 110 return $xlogin->getAuthenticated($auth, $name, $clear=true) ?? $user; 109 if ($xu = $xlogin->getAuthenticated($auth, $name, $clear=true, $guest)) { 110 if ($guest) { 111 // Guest is not allowed to access admin page, so replace 112 // use site URL if login redirect looks like the admin 113 // page so that user does not get an error page. 114 add_filter('login_redirect', function($url) { 115 if (strpos($url, admin_url()) == 0) 116 return site_url(); 117 return $url; 118 }); 119 } 120 return $xu; 121 } 111 122 return $user; 112 123 } /*}}}*/, 10, 3); -
xlogin/trunk/lib/XLogin.php
r2264830 r2310981 18 18 use WP_User; 19 19 20 use Exception;20 use Throwable; 21 21 22 22 /** … … 63 63 * aliases' (email addresses). It is thus possible to allow multiple 64 64 * users to share the same WordPress account without shared knowledge 65 * of the WordPress password. 65 * of the WordPress password. A 'guest' user may be configured as 66 * wildcard for no-match situation. 66 67 * 67 68 * For privacy consideration, external aliases are stored in the … … 78 79 /** @var string Facebook Graph API version to request. */ 79 80 public static $FACEBOOK_GRAPH_API_VERS = 'v3.3'; 81 82 /** @var array Capabilities that a guest user must not have. */ 83 public static $GUEST_USER_FORBIDDEN_CAPS = [ 84 // TODO: configurable via settings 85 'edit_posts', // c.f. anyone above 'subscriber' 86 'delete_posts', // c.f. 'contributor' or above 87 'publish_posts', // c.f. 'author' or above 88 'edit_pages', // c.f. 'editor' or above 89 'manage_options', // c.f. 'administrator' or above 90 ]; 91 92 /** @var array Capabilities to disable for guest user. */ 93 public static $GUEST_USER_DISABLED_CAPS = [ 94 // TODO: configurable via settings 95 'read', // access to dashboard and profile 96 'edit_user', // profile update (via REST API) 97 ]; 80 98 81 99 /** @var array External login service instances by name. */ … … 452 470 * @param string $name External auth user name if known. 453 471 * @param boolean $clear Clear external auth from session. 472 * @param boolean &$guest Tell if authenticated user is a guest. 454 473 * @return WP_User|WP_Error|NULL Null if no external user. 455 474 */ 456 public function getAuthenticated($type, $name=null, $clear=false) /*{{{*/ 475 public function getAuthenticated( 476 $type, 477 $name=null, 478 $clear=false, 479 &$guest=false 480 ) /*{{{*/ 457 481 { 458 482 $this->logDebug("getAuthenticated(): type=$type name=$name"); 483 $guest = null; 459 484 460 485 if (!($xu = $this->flowAttrGet("$type-xuser"))) … … 492 517 assert($xu['id'] == $user->ID); 493 518 $this->xu_cache[$user->ID] = $xu; 519 $guest = $xu['guest'] ?? false; 494 520 } 495 521 return $user; … … 576 602 $uri = $this->url_base.$this->getCallbackPathRelative($cb, $type); 577 603 return $uri; 604 } /*}}}*/ 605 606 /** 607 * Get configuration items for customization. 608 * @return array Customization config options. 609 */ 610 public function getCustomization() /*{{{*/ 611 { 612 $options = $this->getOptions(); 613 return $options['customize'] ?? null; 578 614 } /*}}}*/ 579 615 … … 856 892 return null; 857 893 894 // Get guest login name from ID. 895 if (is_int($guest = $provider['guest'] ?? null)) { 896 if ($guest = get_user_by('ID', $guest)) 897 $provider['guest'] = $guest->user_login; 898 else 899 unset($provider['guest']); 900 $this->options['providers'][$type] = $provider; 901 } 902 903 // Decrypt auth config. 858 904 if ($config = $provider['config'] ?? null) { 859 905 if (!is_array($config)) … … 1014 1060 * external user information, and is thus sensitive to WordPress version. 1015 1061 * 1062 * @param boolean &$guest Flag to indicate if imported user is guest. 1016 1063 * @return boolean True if import is done. 1017 1064 */ 1018 public function importXUser( ) /*{{{*/1065 public function importXUser(&$guest=false) /*{{{*/ 1019 1066 { 1020 1067 global $current_user; 1021 1068 global $userdata, $user_ID, $user_identity, $user_email; 1022 1069 1070 $guest = null; 1023 1071 if (!($xu = $this->getXUserInfo($user_ID))) 1024 1072 return false; … … 1046 1094 = $user_email 1047 1095 = $xu['email']; 1096 $guest = $xu['guest'] ?? false; 1097 1098 // Tighten access control for guests. 1099 if ($guest) { 1100 // Disable capabilities for guest. 1101 add_filter('user_has_cap', function($allcaps, $caps, $args) { 1102 $disabled = static::$GUEST_USER_DISABLED_CAPS; 1103 if (!$disabled) 1104 return $allcaps; 1105 $wanted = $args[0]; 1106 if ($allcaps[$wanted] ?? true && in_array($wanted, $disabled)) 1107 $allcaps[$wanted] = false; 1108 return $allcaps; 1109 }, 10, 3); 1110 } 1048 1111 1049 1112 return true; … … 1149 1212 $info = call_user_func($info); 1150 1213 call_user_func($this->dbg_logger, $info); 1214 } /*}}}*/ 1215 1216 /** 1217 * Check if a WP user is acceptable as guest via external login. 1218 * 1219 * A guest user is one authenticated by an external login service 1220 * but with an alias not recognized, and should have minimal 1221 * privilege. Generally want 'subscriber' level, but role is 1222 * fully customizable in WP, so check for some capabilities. 1223 * 1224 * @param WP_User $user User to check. 1225 * @param string &$emsg Error message if not acceptable. 1226 * @return boolean True if user acceptable as guest. 1227 */ 1228 public function isAcceptableGuest($user, &$emsg=null) /*{{{*/ 1229 { 1230 $emsg = null; 1231 1232 if (!$user instanceof WP_User) { 1233 $emsg = 'Invalid user.'; 1234 return false; 1235 } 1236 1237 foreach (static::$GUEST_USER_FORBIDDEN_CAPS ?? [] as $forbid) { 1238 if ($user->has_cap($forbid)) { 1239 $emsg = 'User is too privileged to be guest login.'; 1240 return false; 1241 } 1242 } 1243 1244 return true; 1151 1245 } /*}}}*/ 1152 1246 … … 1239 1333 if (!$user) 1240 1334 $user = $this->resolveXUserByAlias('email', $email); 1335 1336 // Attempt guest user if no specific one matched. 1337 $guest = null; 1338 if (!$user && !empty($config['guest'])) { 1339 if ($guest = get_user_by('login', $config['guest'])) { 1340 if ($this->isAcceptableGuest($guest)) 1341 $user = $guest; 1342 else 1343 $guest = null; 1344 } 1345 } 1346 1241 1347 if (!$user) { 1242 1348 $this->setError( … … 1253 1359 $xu['id'] = $user->ID; 1254 1360 $xu['xtype'] = $type; 1361 if ($guest) 1362 $xu['guest'] = true; 1255 1363 if (!$this->flowAttrSet("$type-xuser", $xu)) 1256 1364 return null; … … 1572 1680 ]; 1573 1681 1682 // Customization config items. 1683 if (is_array($input['customize']??null)) { 1684 foreach ($input['customize'] as $key => $val) { 1685 switch ($key) { 1686 case 'login_buttons_info': 1687 try { 1688 $val = strval($val); 1689 $data['customize'][$key] = sanitize_text_field($val); 1690 } 1691 catch (Throwable $err) { 1692 $this->logDebug("invalid customization '$key' value"); 1693 } 1694 break; 1695 default: 1696 $this->logDebug("unknown customization '$key'"); 1697 break; 1698 } 1699 } 1700 } 1701 1574 1702 // Login/auth provider configuration. 1575 1703 if (is_array($input['providers']??null)) { … … 1588 1716 if ($provider['override'] ?? false) { 1589 1717 $slot['override'] = true; 1718 } 1719 1720 $guest = $provider['guest'] ?? null; 1721 if (is_string($guest) && $guest != '') { 1722 $guest = get_user_by('login', $guest); 1723 if ($guest && $this->isAcceptableGuest($guest)) 1724 $slot['guest'] = $guest->ID; 1590 1725 } 1591 1726 … … 1639 1774 1640 1775 return true; 1776 } /*}}}*/ 1777 1778 /** 1779 * Update customization configuration items. 1780 * @param array $data Config data. 1781 * @return array|WP_Error Updated config data or error. 1782 */ 1783 public function updateCustomization($data) /*{{{*/ 1784 { 1785 $options = $this->getOptions(); 1786 $options['customize'] = $data; 1787 $options = $this->sanitizeOptions($options); 1788 1789 if (!update_option($this->getOptionsName(), $options)) 1790 return new WP_Error('server_error', 'Failed to save option.'); 1791 1792 // Reload options. 1793 $this->getOptions($cache=false); 1794 return $this->getCustomization(); 1641 1795 } /*}}}*/ 1642 1796 -
xlogin/trunk/lib/XLoginApi.php
r2264830 r2310981 43 43 'error_description' => $result->get_error_message(), 44 44 ]; 45 } /*}}}*/ 46 47 /** 48 * REST callback to check permission for general admin operations. 49 * @return boolean 50 */ 51 public static function checkPermAdmin() /*{{{*/ 52 { 53 return current_user_can('manage_options'); 45 54 } /*}}}*/ 46 55 … … 231 240 232 241 /** 242 * Perform a miscellaneous admin operation. 243 * @param string $op Operation to perform. 244 * @param array $params Parameters for the operation. 245 * @return array|WP_Error Operation result, or WP_Error. 246 */ 247 public function performAdminOp($op, $params) /*{{{*/ 248 { 249 $result = []; 250 $SUCCESS =& $result['success']; 251 $ERR_MSG =& $result['err_msg']; 252 253 switch ($op) { 254 case 'check-guest': 255 $guestLogin = $params['login'] ?? null; 256 if ($guestLogin == '') 257 return new WP_Error('input-invalid', 'Missing guest login.'); 258 $guestUser = static::lookupWpUser($guestLogin); 259 if (!$guestUser) { 260 $SUCCESS = false; 261 $ERR_MSG = 'Invalid guest login.'; 262 break; 263 } 264 if ($SUCCESS = $this->xlogin->isAcceptableGuest($guestUser, $emsg)) 265 $result['login'] = $guestUser->user_login; 266 else 267 $ERR_MSG = $emsg; 268 break; 269 default: 270 return new WP_Error( 271 'input-invalid', 272 "Unknown admin operation '$op'." 273 ); 274 } 275 return $result; 276 } /*}}}*/ 277 278 /** 233 279 * Register an external login. 234 280 * @param string $alias External alias, e.g. 'email:jdoe@example.com'. -
xlogin/trunk/readme.txt
r2265492 r2310981 1 1 === XLogin === 2 2 Contributors: scoop082110 3 Donate link: https://www.paypal.me/scoop0821104 3 Tags: login, oauth, google, yahoo, facebook 5 4 Requires at least: 5.3 6 Tested up to: 5. 37 Stable tag: 1. 05 Tested up to: 5.4 6 Stable tag: 1.1 8 7 Requires PHP: 7.0 9 8 License: GPLv2 or later … … 59 58 WordPress session when he logs in with Facebook. 60 59 60 Finally XLogin has the notion of 'guest' user. When an email address 61 provided by an external service does not correspond to a specific 62 WordPress user, XLogin will proceed with a guest WordPress user 63 configured for the service. There are plugins that control access to 64 content based on user roles; a site can combine such with XLogin to 65 control what is visible to guests. Note that guest WordPress users 66 should have minimal privileges. XLogin has safeguards to ensure, for 67 example, that a guest cannot edit posts. It also filters out guest's 68 permission to access the dashboard or to update their own profile. 69 61 70 == Installation == 62 71 … … 94 103 = Does this work with WordPress version X? = 95 104 96 This plugin is developed on WordPress 5.3. It has not been tried on any97 other version.105 This plugin was originally developed on WordPress 5.3. It works on 5.3 106 and 5.4, but has not been tried on any other version. 98 107 99 108 = Does this work with PHP 5.x? = … … 177 186 == Changelog == 178 187 188 = 1.1 = 189 * Custom message to display with external login buttons. 190 * Guest user for unmatched email address. 191 179 192 = 1.0 = 180 193 * First version published.
Note: See TracChangeset
for help on using the changeset viewer.