blob: 1286c5272b7a94bb4c8ed1ebb181a70483a068bc [file] [log] [blame]
Nico Huberee52fbc2023-06-24 11:52:57 +00001<?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 */
34define('OPENID_NS_AX', 'http://openid.net/srv/ax/1.0');
35
36/** @ignore */
37global $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 */
71function ax_xrds_types() {
72 return array(OPENID_NS_AX);
73}
74
75/**
76 * @see hook_response()
77 */
78function 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 */
134function 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 */
153function 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 */
212function 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 */
253function 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 */
290function _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?>