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 Attribute Exchange extension. |
| 26 | * |
| 27 | * |
| 28 | * @package simpleid |
| 29 | * @subpackage extensions |
| 30 | * @filesource |
| 31 | */ |
| 32 | |
| 33 | /** Namespace for the AX extension */ |
| 34 | define('OPENID_NS_AX', 'http://openid.net/srv/ax/1.0'); |
| 35 | |
| 36 | /** @ignore */ |
| 37 | global $ax_sreg_map; |
| 38 | |
| 39 | /** |
| 40 | * A mapping between Type URIs defined for Attribute Exchange and the corresponding |
| 41 | * property for the Simple Registration Extension |
| 42 | * |
| 43 | * @link http://www.axschema.org/types/#sreg |
| 44 | * @global array |
| 45 | */ |
| 46 | $ax_sreg_map = array( |
| 47 | 'http://axschema.org/namePerson/friendly' => 'nickname', |
| 48 | 'http://axschema.org/contact/email' => 'email', |
| 49 | 'http://axschema.org/namePerson' => 'fullname', |
| 50 | 'http://axschema.org/birthDate' => 'dob', |
| 51 | 'http://axschema.org/person/gender' => 'gender', |
| 52 | 'http://axschema.org/contact/postalCode/home' => 'postcode', |
| 53 | 'http://axschema.org/contact/country/home' => 'country', |
| 54 | 'http://axschema.org/pref/language' => 'language', |
| 55 | 'http://axschema.org/pref/timezone' => 'timezone', |
| 56 | 'http://openid.net/schema/namePerson/friendly' => 'nickname', |
| 57 | 'http://openid.net/schema/contact/internet/email' => 'email', |
| 58 | 'http://openid.net/schema/gender' => 'gender', |
| 59 | 'http://openid.net/schema/contact/postalCode/home' => 'postcode', |
| 60 | 'http://openid.net/schema/contact/country/home' => 'country', |
| 61 | 'http://openid.net/schema/language/pref' => 'language', |
| 62 | 'http://openid.net/schema/timezone' => 'timezone' |
| 63 | ); |
| 64 | |
| 65 | /** |
| 66 | * Returns the support for AX in SimpleID XRDS document |
| 67 | * |
| 68 | * @return array |
| 69 | * @see hook_xrds_types() |
| 70 | */ |
| 71 | function ax_xrds_types() { |
| 72 | return array(OPENID_NS_AX); |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * @see hook_response() |
| 77 | */ |
| 78 | function ax_response($assertion, $request) { |
| 79 | global $user; |
| 80 | global $version; |
| 81 | global $ax_sreg_map; |
| 82 | |
| 83 | // We only deal with positive assertions |
| 84 | if (!$assertion) return array(); |
| 85 | |
| 86 | // We only respond if the extension is requested |
| 87 | if (!openid_extension_requested(OPENID_NS_AX, $request)) return array(); |
| 88 | |
| 89 | $request = openid_extension_filter_request(OPENID_NS_AX, $request); |
| 90 | if (!isset($request['mode'])) return array(); |
| 91 | $mode = $request['mode']; |
| 92 | |
| 93 | $response = array(); |
| 94 | $alias = openid_extension_alias(OPENID_NS_AX); |
| 95 | $response['openid.ns.' . $alias] = OPENID_NS_AX; |
| 96 | |
| 97 | if ($mode == 'fetch_request') { |
| 98 | $response['openid.' . $alias . '.mode'] = 'fetch_response'; |
| 99 | |
| 100 | $required = (isset($request['required'])) ? explode(',', $request['required']) : array(); |
| 101 | $optional = (isset($request['if_available'])) ? explode(',', $request['if_available']) : array(); |
| 102 | $fields = array_merge($required, $optional); |
| 103 | |
| 104 | foreach ($fields as $field) { |
| 105 | $type = $request['type.' . $field]; |
| 106 | $response['openid.' . $alias . '.type.' . $field] = $type; |
| 107 | $value = _ax_get_value($type); |
| 108 | |
| 109 | if ($value == NULL) { |
| 110 | $response['openid.' . $alias . '.count.' . $field] = 0; |
| 111 | } elseif (is_array($value)) { |
| 112 | $response['openid.' . $alias . '.count.' . $field] = count($value); |
| 113 | for ($i = 0; $i < count($value); $i++) { |
| 114 | $response['openid.' . $alias . '.value.' . $field . '.' . ($i + 1)] = $value[$i]; |
| 115 | } |
| 116 | } else { |
| 117 | $response['openid.' . $alias . '.value.' . $field] = $value; |
| 118 | } |
| 119 | } |
| 120 | } elseif ($mode == 'store_request') { |
| 121 | // Sadly, we don't support storage at this stage |
| 122 | $response['openid.' . $alias . '.mode'] = 'store_response_failure'; |
| 123 | $response['openid.' . $alias . '.error'] = 'OpenID provider does not support storage of attributes'; |
| 124 | } |
| 125 | |
| 126 | return $response; |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * Returns an array of fields that need signing. |
| 131 | * |
| 132 | * @see hook_signed_fields() |
| 133 | */ |
| 134 | function ax_signed_fields($response) { |
| 135 | // We only respond if the extension is requested |
| 136 | if (!openid_extension_requested(OPENID_NS_AX, $response)) return array(); |
| 137 | |
| 138 | $fields = array_keys(openid_extension_filter_request(OPENID_NS_AX, $response)); |
| 139 | $alias = openid_extension_alias(OPENID_NS_AX); |
| 140 | $signed_fields = array(); |
| 141 | |
| 142 | if (isset($response['openid.ns.' . $alias])) $signed_fields[] = 'ns.' . $alias; |
| 143 | foreach ($fields as $field) { |
| 144 | if (isset($response['openid.' . $alias . '.' . $field])) $signed_fields[] = $alias . '.' . $field; |
| 145 | } |
| 146 | |
| 147 | return $signed_fields; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * @see hook_consent_form() |
| 152 | */ |
| 153 | function ax_consent_form($request, $response, $rp) { |
| 154 | global $user; |
| 155 | |
| 156 | // We only respond if the extension is requested |
| 157 | if (!openid_extension_requested(OPENID_NS_AX, $request)) return ''; |
| 158 | |
| 159 | $request = openid_extension_filter_request(OPENID_NS_AX, $request); |
| 160 | if (!isset($request['mode'])) return ''; |
| 161 | $mode = $request['mode']; |
| 162 | |
| 163 | $xtpl2 = new XTemplate('extensions/ax/ax.xtpl'); |
| 164 | |
| 165 | if ($mode == 'fetch_request') { |
| 166 | $xtpl2->assign('alias', openid_extension_alias(OPENID_NS_AX)); |
| 167 | |
| 168 | $required = (isset($request['required'])) ? explode(',', $request['required']) : array(); |
| 169 | $optional = (isset($request['if_available'])) ? explode(',', $request['if_available']) : array(); |
| 170 | $fields = array_merge($required, $optional); |
| 171 | $i = 1; |
| 172 | |
| 173 | foreach ($fields as $field) { |
| 174 | $type = $request['type.' . $field]; |
| 175 | $value = _ax_get_value($type); |
| 176 | |
| 177 | $xtpl2->assign('name', htmlspecialchars($type, ENT_QUOTES, 'UTF-8')); |
| 178 | $xtpl2->assign('id', $i); |
| 179 | |
| 180 | if (is_array($value)) { |
| 181 | $xtpl2->assign('value', htmlspecialchars(implode(',', $value), ENT_QUOTES, 'UTF-8')); |
| 182 | } elseif ($value != NULL) { |
| 183 | $xtpl2->assign('value', htmlspecialchars($value, ENT_QUOTES, 'UTF-8')); |
| 184 | } |
| 185 | |
| 186 | $xtpl2->assign('checked', (in_array($field, $required) || !isset($rp['ax_consents']) || in_array($field, $rp['ax_consents'])) ? 'checked="checked"' : ''); |
| 187 | $xtpl2->assign('disabled', (in_array($field, $required)) ? 'disabled="disabled"' : ''); |
| 188 | if (in_array($field, $required)) $xtpl2->parse('fetch_request.ax.required'); |
| 189 | |
| 190 | $xtpl2->parse('fetch_request.ax'); |
| 191 | |
| 192 | $i++; |
| 193 | } |
| 194 | |
| 195 | $xtpl2->assign('ax_data', t('SimpleID will also be sending the following information to the site.')); |
| 196 | $xtpl2->assign('name_label', t('Type URL')); |
| 197 | $xtpl2->assign('value_label', t('Value')); |
| 198 | |
| 199 | $xtpl2->parse('fetch_request'); |
| 200 | return $xtpl2->text('fetch_request'); |
| 201 | } elseif ($mode == 'store_request') { |
| 202 | // Sadly, we don't support storage at this stage |
| 203 | $xtpl2->assign('store_request_message', t('This web site requested to store information about you on SimpleID. Sadly, SimpleID does not support this feature.')); |
| 204 | $xtpl2->parse('store_request'); |
| 205 | return $xtpl2->text('store_request'); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * @see hook_consent() |
| 211 | */ |
| 212 | function ax_consent($form_request, &$response, &$rp) { |
| 213 | // We only respond if the extension is requested |
| 214 | if (!openid_extension_requested(OPENID_NS_AX, $response)) return array(); |
| 215 | |
| 216 | $fields = array_keys(openid_extension_filter_request(OPENID_NS_AX, $response)); |
| 217 | $alias = openid_extension_alias(OPENID_NS_AX); |
| 218 | |
| 219 | foreach ($fields as $field) { |
| 220 | if ((strpos($field, 'value.') !== 0) && (strpos($field, 'count.') !== 0)) continue; |
| 221 | |
| 222 | $type_alias = (strpos($field, '.', 6) === FALSE) ? substr($field, 6) : substr($field, strpos($field, '.', 6) - 6); |
| 223 | $type = $response['openid.' . $alias . '.type.' . $type_alias]; |
| 224 | |
| 225 | if (isset($response['openid.' . $alias . '.' . $field])) { |
| 226 | if (!in_array($type, $form_request['ax_consents'])) { |
| 227 | unset($response['openid.' . $alias . '.' . $field]); |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | foreach ($fields as $field) { |
| 232 | if (strpos($field, 'type.') !== 0) continue; |
| 233 | $type = $response['openid.' . $alias . '.' . $field]; |
| 234 | |
| 235 | if (isset($response['openid.' . $alias . '.' . $field])) { |
| 236 | if (!in_array($type, $form_request['ax_consents'])) { |
| 237 | unset($response['openid.' . $alias . '.' . $field]); |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | if (count(array_keys(openid_extension_filter_request(OPENID_NS_AX, $response))) == 0) { |
| 243 | // We have removed all the responses, so we remove the namespace as well |
| 244 | unset($response['openid.ns.' . $alias]); |
| 245 | } |
| 246 | |
| 247 | $rp['ax_consents'] = $form_request['ax_consents']; |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * @see hook_page_profile() |
| 252 | */ |
| 253 | function ax_page_profile() { |
| 254 | global $user; |
| 255 | $xtpl2 = new XTemplate('extensions/ax/ax.xtpl'); |
| 256 | |
| 257 | if (isset($user['ax'])) { |
| 258 | foreach ($user['ax'] as $name => $value) { |
| 259 | $xtpl2->assign('name', htmlspecialchars($name, ENT_QUOTES, 'UTF-8')); |
| 260 | $xtpl2->assign('value', htmlspecialchars($value, ENT_QUOTES, 'UTF-8')); |
| 261 | $xtpl2->parse('user_page.ax'); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | $xtpl2->assign('ax_data', t('SimpleID will send the following information to sites which supports the Attribute Exchange Extension. If you have also supplied OpenID Connect user information in your identity, or have the Simple Registration Extension installed, these may also be sent as part of this Extension.')); |
| 266 | $xtpl2->assign('edit_identity_file', t('To change these, <a href="!url">edit your identity file</a>.', array('!url' => 'http://simpleid.org/docs/1/identity-files/'))); |
| 267 | $xtpl2->assign('name_label', t('Type URL')); |
| 268 | $xtpl2->assign('value_label', t('Value')); |
| 269 | |
| 270 | $xtpl2->parse('user_page'); |
| 271 | |
| 272 | return array(array( |
| 273 | 'id' => 'ax', |
| 274 | 'title' => t('Attribute Exchange Extension'), |
| 275 | 'content' => $xtpl2->text('user_page') |
| 276 | )); |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * Looks up the value of a specified Attribute Exchange Extension type URI. |
| 281 | * |
| 282 | * This function looks up the ax section of the user's identity file. If the |
| 283 | * specified type cannot be found, it looks up the corresponding field in the |
| 284 | * OpenID Connect user information (user_info section) and the Simple Registration |
| 285 | * Extension (sreg section). |
| 286 | * |
| 287 | * @param string $type the type URI to look up |
| 288 | * @return string the value or NULL if not found |
| 289 | */ |
| 290 | function _ax_get_value($type) { |
| 291 | global $user; |
| 292 | global $ax_sreg_map; |
| 293 | |
| 294 | if (isset($user['ax'][$type])) { |
| 295 | return $user['ax'][$type]; |
| 296 | } else { |
| 297 | // Look up OpenID Connect |
| 298 | switch ($type) { |
| 299 | case 'http://axschema.org/namePerson/friendly': |
| 300 | if (isset($user['user_info']['nickname'])) return $user['user_info']['nickname']; |
| 301 | break; |
| 302 | case 'http://axschema.org/contact/email': |
| 303 | if (isset($user['user_info']['email'])) return $user['user_info']['email']; |
| 304 | break; |
| 305 | case 'http://axschema.org/namePerson': |
| 306 | if (isset($user['user_info']['name'])) return $user['user_info']['name']; |
| 307 | break; |
| 308 | case 'http://axschema.org/pref/timezone': |
| 309 | if (isset($user['user_info']['zoneinfo'])) return $user['user_info']['zoneinfo']; |
| 310 | break; |
| 311 | case 'http://axschema.org/person/gender': |
| 312 | if (isset($user['user_info']['gender'])) return strtoupper(substr($user['user_info']['gender'], 0, 1)); |
| 313 | break; |
| 314 | case 'http://axschema.org/contact/postalCode/home': |
| 315 | if (isset($user['user_info']['address']['postal_code'])) return $user['user_info']['address']['postcal_code']; |
| 316 | break; |
| 317 | } |
| 318 | |
| 319 | // Look up sreg |
| 320 | if (isset($ax_sreg_map[$type]) && isset($user['sreg'][$ax_sreg_map[$type]])) { |
| 321 | return $user['sreg'][$ax_sreg_map[$type]]; |
| 322 | } else { |
| 323 | return NULL; |
| 324 | } |
| 325 | } |
| 326 | } |
| 327 | ?> |