Nico Huber | ee52fbc | 2023-06-24 11:52:57 +0000 | [diff] [blame] | 1 | <?php |
| 2 | /* |
| 3 | * SimpleID |
| 4 | * |
| 5 | * Copyright (C) Kelvin Mo 2009 |
| 6 | * |
| 7 | * This program is free software; you can redistribute it and/or |
| 8 | * modify it under the terms of the GNU General Public |
| 9 | * License as published by the Free Software Foundation; either |
| 10 | * version 2 of the License, or (at your option) any later version. |
| 11 | * |
| 12 | * This program is distributed in the hope that it will be useful, |
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 | * General Public License for more details. |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public |
| 18 | * License along with this program; if not, write to the Free |
| 19 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| 20 | * |
| 21 | * $Id$ |
| 22 | */ |
| 23 | |
| 24 | /** |
| 25 | * Implements the popup and icon modes from the User Interface extension |
| 26 | * |
| 27 | * @package simpleid |
| 28 | * @subpackage extensions |
| 29 | * @filesource |
| 30 | */ |
| 31 | |
| 32 | /** Namespace for the User Interface extension */ |
| 33 | define('OPENID_NS_UI', 'http://specs.openid.net/extensions/ui/1.0'); |
| 34 | |
| 35 | /** |
| 36 | * Returns the popup mode in SimpleID XRDS document |
| 37 | * |
| 38 | * @return array |
| 39 | * @see hook_xrds_types() |
| 40 | */ |
| 41 | function ui_xrds_types() { |
| 42 | return array( |
| 43 | 'http://specs.openid.net/extensions/ui/1.0/mode/popup', |
| 44 | 'http://specs.openid.net/extensions/ui/1.0/icon' |
| 45 | ); |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * Detects the openid.ui.x-has-session parameter and processes it accordingly. |
| 50 | * |
| 51 | * @return array |
| 52 | * @see hook_response() |
| 53 | */ |
| 54 | function ui_response($assertion, $request) { |
| 55 | global $user; |
| 56 | global $version; |
| 57 | |
| 58 | // We only deal with negative assertions |
| 59 | if ($assertion) return array(); |
| 60 | |
| 61 | // We only respond if the extension is requested |
| 62 | if (!openid_extension_requested(OPENID_NS_UI, $request)) return array(); |
| 63 | |
| 64 | // We only deal with openid.ui.x-has-session requests |
| 65 | $filtered_request = openid_extension_filter_request(OPENID_NS_UI, $request); |
| 66 | if (!isset($filtered_request['mode']) || ($filtered_request['mode'] != 'x-has-session')) return array(); |
| 67 | |
| 68 | // If user is null, there is no active session |
| 69 | if ($user == NULL) return array(); |
| 70 | |
| 71 | // There is an active session |
| 72 | $alias = openid_extension_alias(OPENID_NS_UI); |
| 73 | $response = array(); |
| 74 | |
| 75 | $response['openid.ns.' . $alias] = OPENID_NS_UI; |
| 76 | $response['openid.' . $alias . '.mode'] = 'x-has-session'; |
| 77 | |
| 78 | return $response; |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Returns an array of fields that need signing. |
| 83 | * |
| 84 | * @see hook_signed_fields() |
| 85 | */ |
| 86 | function ui_signed_fields($response) { |
| 87 | // We only respond if the extension is requested |
| 88 | if (!openid_extension_requested(OPENID_NS_UI, $response)) return array(); |
| 89 | |
| 90 | $fields = array_keys(openid_extension_filter_request(OPENID_NS_UI, $response)); |
| 91 | $alias = openid_extension_alias(OPENID_NS_UI); |
| 92 | $signed_fields = array(); |
| 93 | |
| 94 | if (isset($response['openid.ns.' . $alias])) $signed_fields[] = 'ns.' . $alias; |
| 95 | foreach ($fields as $field) { |
| 96 | if (isset($response['openid.' . $alias . '.' . $field])) $signed_fields[] = $alias . '.' . $field; |
| 97 | } |
| 98 | |
| 99 | return $signed_fields; |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Detects the presence of the UI extension and modifies the login form |
| 104 | * accordingly. |
| 105 | * |
| 106 | * @param string $destination |
| 107 | * @param string $state |
| 108 | * @see hook_user_login_form() |
| 109 | */ |
| 110 | function ui_user_login_form($destination, $state) { |
| 111 | if (($destination != 'continue') || (!$state)) return; |
| 112 | |
| 113 | $request = unpickle($state); |
| 114 | openid_parse_request($request); |
| 115 | |
| 116 | // Skip if popup does not exist |
| 117 | if (!openid_extension_requested(OPENID_NS_UI, $request)) return; |
| 118 | |
| 119 | $filtered_request = openid_extension_filter_request(OPENID_NS_UI, $request); |
| 120 | |
| 121 | if (isset($filtered_request['mode']) && ($filtered_request['mode'] == 'popup')) _ui_insert_css_js(); |
| 122 | |
| 123 | return; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Detects the presence of the UI extension and modifies the relying party |
| 128 | * verification form accordingly. |
| 129 | * |
| 130 | * @param array $request |
| 131 | * @param array $response |
| 132 | * @param array $rp |
| 133 | * @return string |
| 134 | * @see hook_consent_form() |
| 135 | */ |
| 136 | function ui_consent_form($request, $response, $rp) { |
| 137 | // Skip if popup does not exist |
| 138 | if (!openid_extension_requested(OPENID_NS_UI, $request)) return ''; |
| 139 | |
| 140 | $filtered_request = openid_extension_filter_request(OPENID_NS_UI, $request); |
| 141 | |
| 142 | if (isset($filtered_request['mode']) && ($filtered_request['mode'] == 'popup')) _ui_insert_css_js(); |
| 143 | |
| 144 | if (isset($filtered_request['icon']) && ($filtered_request['icon'] == 'true')) { |
| 145 | global $xtpl; |
| 146 | |
| 147 | $realm = $request['openid.realm']; |
| 148 | $icon_url = simpleid_url('ui/icon', 'realm=' . rawurlencode($realm) . '&tk=' . _ui_icon_token($realm)); |
| 149 | |
| 150 | $xtpl->assign('icon_url', htmlspecialchars($icon_url, ENT_QUOTES, 'UTF-8')); |
| 151 | $xtpl->parse('main.openid_consent.icon'); |
| 152 | } |
| 153 | |
| 154 | return ''; |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * Specifies that the OpenID response should be sent via the fragment |
| 159 | * |
| 160 | */ |
| 161 | function ui_indirect_response($url, $response) { |
| 162 | global $openid_ns_to_alias; |
| 163 | if (!array_key_exists(OPENID_NS_UI, $openid_ns_to_alias)) return NULL; |
| 164 | |
| 165 | // Cheat - if we run this, then the redirect page will also be themed! |
| 166 | _ui_insert_css_js(); |
| 167 | |
| 168 | if (strstr($url, '#')) { |
| 169 | return OPENID_RESPONSE_FRAGMENT; |
| 170 | } else { |
| 171 | return NULL; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Adds an extra route to the SimpleWeb framework. |
| 177 | */ |
| 178 | function ui_routes() { |
| 179 | return array('ui/icon' => 'ui_icon'); |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Returns an icon. |
| 184 | */ |
| 185 | function ui_icon() { |
| 186 | if (!isset($_GET['realm']) || !isset($_GET['tk']) || ($_GET['tk'] != _ui_icon_token($_GET['realm']))) { |
| 187 | header_response_code('404 Not Found'); |
| 188 | indirect_fatal_error(t('Invalid UI icon parameters.')); |
| 189 | } |
| 190 | |
| 191 | $realm = $_GET['realm']; |
| 192 | $icon_res = _ui_get_icon($realm); |
| 193 | |
| 194 | if ($icon_res === NULL) { |
| 195 | header_response_code('404 Not Found'); |
| 196 | indirect_fatal_error(t('Unable to get icon.')); |
| 197 | } |
| 198 | |
| 199 | header('Via: ' . $icon_res['protocol'] . ' simpleid-ui-icon-' . md5($realm)); |
| 200 | header('Cache-Control: max-age=86400'); |
| 201 | header('Content-Type: ' . $icon_res['headers']['content-type']); |
| 202 | if (isset($icon_res['headers']['content-encoding'])) header('Content-Encoding: ' . $icon_res['headers']['content-encoding']); |
| 203 | print $icon_res['data']; |
| 204 | } |
| 205 | |
| 206 | /** |
| 207 | * Inserts the necessary CSS and JavaScript code to implement the popup mode |
| 208 | * from the User Interface extension. |
| 209 | */ |
| 210 | function _ui_insert_css_js() { |
| 211 | global $xtpl; |
| 212 | |
| 213 | $css = (isset($xtpl->vars['css'])) ? $xtpl->vars['css'] : ''; |
| 214 | $js = (isset($xtpl->vars['javascript'])) ? $xtpl->vars['javascript'] : ''; |
| 215 | |
| 216 | $xtpl->assign('css', $css . '@import url(' . get_base_path() . 'extensions/ui/ui.css);'); |
| 217 | $xtpl->assign('javascript', $js . '<script src="' . get_base_path() . 'extensions/ui/ui.js" type="text/javascript"></script>'); |
| 218 | } |
| 219 | |
| 220 | /** |
| 221 | * Attempts to obtain an icon from a RP |
| 222 | * |
| 223 | * @param string $realm the openid.realm parameter |
| 224 | * @return array the response from {@link http_make_request()} with the discovered URL of the |
| 225 | * RP's icon |
| 226 | */ |
| 227 | function _ui_get_icon($realm) { |
| 228 | $rp_info = simpleid_get_rp_info($realm); |
| 229 | |
| 230 | if (isset($rp_info['ui_icon'])) return $rp_info['ui_icon']; |
| 231 | |
| 232 | $services = discovery_xrds_services_by_type($rp_info['services'], 'http://specs.openid.net/extensions/ui/icon'); |
| 233 | |
| 234 | if ($services) { |
| 235 | $icon_url = $services[0]['uri']; |
| 236 | |
| 237 | $icon_res = http_make_request($icon_url); |
| 238 | if (isset($icon_res['http-error'])) { |
| 239 | return NULL; |
| 240 | } |
| 241 | |
| 242 | $rp_info['ui_icon'] = $icon_res; |
| 243 | simpleid_set_rp_info($realm, $rp_info); |
| 244 | } else { |
| 245 | return NULL; |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Returns a token to be used when requesting the icon. |
| 251 | * |
| 252 | * The token is used to prevent flooding SimpleID with external requests. |
| 253 | * |
| 254 | * @param string $realm the openid.realm parameter |
| 255 | * @return string the token |
| 256 | */ |
| 257 | function _ui_icon_token($realm) { |
| 258 | return get_form_token('q=ui/icon&realm=' . rawurlencode($realm)); |
| 259 | } |
| 260 | ?> |