SReg.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. <?php
  2. /**
  3. * Simple registration request and response parsing and object
  4. * representation.
  5. *
  6. * This module contains objects representing simple registration
  7. * requests and responses that can be used with both OpenID relying
  8. * parties and OpenID providers.
  9. *
  10. * 1. The relying party creates a request object and adds it to the
  11. * {@link Auth_OpenID_AuthRequest} object before making the
  12. * checkid request to the OpenID provider:
  13. *
  14. * $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
  15. * $auth_request->addExtension($sreg_req);
  16. *
  17. * 2. The OpenID provider extracts the simple registration request
  18. * from the OpenID request using {@link
  19. * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
  20. * approval and data, creates an {@link Auth_OpenID_SRegResponse}
  21. * object and adds it to the id_res response:
  22. *
  23. * $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
  24. * $checkid_request);
  25. * // [ get the user's approval and data, informing the user that
  26. * // the fields in sreg_response were requested ]
  27. * $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
  28. * $sreg_req, $user_data);
  29. * $sreg_resp->toMessage($openid_response->fields);
  30. *
  31. * 3. The relying party uses {@link
  32. * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
  33. * from the OpenID response:
  34. *
  35. * $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
  36. * $success_response);
  37. *
  38. * @package OpenID
  39. */
  40. /**
  41. * Import message and extension internals.
  42. */
  43. require_once 'Auth/OpenID/Message.php';
  44. require_once 'Auth/OpenID/Extension.php';
  45. // The data fields that are listed in the sreg spec
  46. global $Auth_OpenID_sreg_data_fields;
  47. $Auth_OpenID_sreg_data_fields = array(
  48. 'fullname' => 'Full Name',
  49. 'nickname' => 'Nickname',
  50. 'dob' => 'Date of Birth',
  51. 'email' => 'E-mail Address',
  52. 'gender' => 'Gender',
  53. 'postcode' => 'Postal Code',
  54. 'country' => 'Country',
  55. 'language' => 'Language',
  56. 'timezone' => 'Time Zone');
  57. /**
  58. * Check to see that the given value is a valid simple registration
  59. * data field name. Return true if so, false if not.
  60. */
  61. function Auth_OpenID_checkFieldName($field_name)
  62. {
  63. global $Auth_OpenID_sreg_data_fields;
  64. if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
  65. return false;
  66. }
  67. return true;
  68. }
  69. // URI used in the wild for Yadis documents advertising simple
  70. // registration support
  71. define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');
  72. // URI in the draft specification for simple registration 1.1
  73. // <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
  74. define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');
  75. // This attribute will always hold the preferred URI to use when
  76. // adding sreg support to an XRDS file or in an OpenID namespace
  77. // declaration.
  78. define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);
  79. Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');
  80. /**
  81. * Does the given endpoint advertise support for simple
  82. * registration?
  83. *
  84. * $endpoint: The endpoint object as returned by OpenID discovery.
  85. * returns whether an sreg type was advertised by the endpoint
  86. */
  87. function Auth_OpenID_supportsSReg($endpoint)
  88. {
  89. return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
  90. $endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
  91. }
  92. /**
  93. * A base class for classes dealing with Simple Registration protocol
  94. * messages.
  95. *
  96. * @package OpenID
  97. */
  98. class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
  99. /**
  100. * Extract the simple registration namespace URI from the given
  101. * OpenID message. Handles OpenID 1 and 2, as well as both sreg
  102. * namespace URIs found in the wild, as well as missing namespace
  103. * definitions (for OpenID 1)
  104. *
  105. * $message: The OpenID message from which to parse simple
  106. * registration fields. This may be a request or response message.
  107. *
  108. * Returns the sreg namespace URI for the supplied message. The
  109. * message may be modified to define a simple registration
  110. * namespace.
  111. *
  112. * @access private
  113. */
  114. static function _getSRegNS($message)
  115. {
  116. $alias = null;
  117. $found_ns_uri = null;
  118. // See if there exists an alias for one of the two defined
  119. // simple registration types.
  120. foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
  121. Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
  122. $alias = $message->namespaces->getAlias($sreg_ns_uri);
  123. if ($alias !== null) {
  124. $found_ns_uri = $sreg_ns_uri;
  125. break;
  126. }
  127. }
  128. if ($alias === null) {
  129. // There is no alias for either of the types, so try to
  130. // add one. We default to using the modern value (1.1)
  131. $found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
  132. if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
  133. 'sreg') === null) {
  134. // An alias for the string 'sreg' already exists, but
  135. // it's defined for something other than simple
  136. // registration
  137. return null;
  138. }
  139. }
  140. return $found_ns_uri;
  141. }
  142. }
  143. /**
  144. * An object to hold the state of a simple registration request.
  145. *
  146. * required: A list of the required fields in this simple registration
  147. * request
  148. *
  149. * optional: A list of the optional fields in this simple registration
  150. * request
  151. *
  152. * @package OpenID
  153. */
  154. class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
  155. var $ns_alias = 'sreg';
  156. /**
  157. * Initialize an empty simple registration request.
  158. */
  159. static function build($required=null, $optional=null,
  160. $policy_url=null,
  161. $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
  162. $cls='Auth_OpenID_SRegRequest')
  163. {
  164. $obj = new $cls();
  165. $obj->required = array();
  166. $obj->optional = array();
  167. $obj->policy_url = $policy_url;
  168. $obj->ns_uri = $sreg_ns_uri;
  169. if ($required) {
  170. if (!$obj->requestFields($required, true, true)) {
  171. return null;
  172. }
  173. }
  174. if ($optional) {
  175. if (!$obj->requestFields($optional, false, true)) {
  176. return null;
  177. }
  178. }
  179. return $obj;
  180. }
  181. /**
  182. * Create a simple registration request that contains the fields
  183. * that were requested in the OpenID request with the given
  184. * arguments
  185. *
  186. * $request: The OpenID authentication request from which to
  187. * extract an sreg request.
  188. *
  189. * $cls: name of class to use when creating sreg request object.
  190. * Used for testing.
  191. *
  192. * Returns the newly created simple registration request
  193. */
  194. static function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
  195. {
  196. $obj = call_user_func_array(array($cls, 'build'),
  197. array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));
  198. // Since we're going to mess with namespace URI mapping, don't
  199. // mutate the object that was passed in.
  200. $m = $request->message;
  201. $obj->ns_uri = $obj->_getSRegNS($m);
  202. $args = $m->getArgs($obj->ns_uri);
  203. if ($args === null || Auth_OpenID::isFailure($args)) {
  204. return null;
  205. }
  206. $obj->parseExtensionArgs($args);
  207. return $obj;
  208. }
  209. /**
  210. * Parse the unqualified simple registration request parameters
  211. * and add them to this object.
  212. *
  213. * This method is essentially the inverse of
  214. * getExtensionArgs. This method restores the serialized simple
  215. * registration request fields.
  216. *
  217. * If you are extracting arguments from a standard OpenID
  218. * checkid_* request, you probably want to use fromOpenIDRequest,
  219. * which will extract the sreg namespace and arguments from the
  220. * OpenID request. This method is intended for cases where the
  221. * OpenID server needs more control over how the arguments are
  222. * parsed than that method provides.
  223. *
  224. * $args == $message->getArgs($ns_uri);
  225. * $request->parseExtensionArgs($args);
  226. *
  227. * $args: The unqualified simple registration arguments
  228. *
  229. * strict: Whether requests with fields that are not defined in
  230. * the simple registration specification should be tolerated (and
  231. * ignored)
  232. */
  233. function parseExtensionArgs($args, $strict=false)
  234. {
  235. foreach (array('required', 'optional') as $list_name) {
  236. $required = ($list_name == 'required');
  237. $items = Auth_OpenID::arrayGet($args, $list_name);
  238. if ($items) {
  239. foreach (explode(',', $items) as $field_name) {
  240. if (!$this->requestField($field_name, $required, $strict)) {
  241. if ($strict) {
  242. return false;
  243. }
  244. }
  245. }
  246. }
  247. }
  248. $this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');
  249. return true;
  250. }
  251. /**
  252. * A list of all of the simple registration fields that were
  253. * requested, whether they were required or optional.
  254. */
  255. function allRequestedFields()
  256. {
  257. return array_merge($this->required, $this->optional);
  258. }
  259. /**
  260. * Have any simple registration fields been requested?
  261. */
  262. function wereFieldsRequested()
  263. {
  264. return count($this->allRequestedFields());
  265. }
  266. /**
  267. * Was this field in the request?
  268. */
  269. function contains($field_name)
  270. {
  271. return (in_array($field_name, $this->required) ||
  272. in_array($field_name, $this->optional));
  273. }
  274. /**
  275. * Request the specified field from the OpenID user
  276. *
  277. * $field_name: the unqualified simple registration field name
  278. *
  279. * required: whether the given field should be presented to the
  280. * user as being a required to successfully complete the request
  281. *
  282. * strict: whether to raise an exception when a field is added to
  283. * a request more than once
  284. */
  285. function requestField($field_name,
  286. $required=false, $strict=false)
  287. {
  288. if (!Auth_OpenID_checkFieldName($field_name)) {
  289. return false;
  290. }
  291. if ($strict) {
  292. if ($this->contains($field_name)) {
  293. return false;
  294. }
  295. } else {
  296. if (in_array($field_name, $this->required)) {
  297. return true;
  298. }
  299. if (in_array($field_name, $this->optional)) {
  300. if ($required) {
  301. unset($this->optional[array_search($field_name,
  302. $this->optional)]);
  303. } else {
  304. return true;
  305. }
  306. }
  307. }
  308. if ($required) {
  309. $this->required[] = $field_name;
  310. } else {
  311. $this->optional[] = $field_name;
  312. }
  313. return true;
  314. }
  315. /**
  316. * Add the given list of fields to the request
  317. *
  318. * field_names: The simple registration data fields to request
  319. *
  320. * required: Whether these values should be presented to the user
  321. * as required
  322. *
  323. * strict: whether to raise an exception when a field is added to
  324. * a request more than once
  325. */
  326. function requestFields($field_names, $required=false, $strict=false)
  327. {
  328. if (!is_array($field_names)) {
  329. return false;
  330. }
  331. foreach ($field_names as $field_name) {
  332. if (!$this->requestField($field_name, $required, $strict=$strict)) {
  333. return false;
  334. }
  335. }
  336. return true;
  337. }
  338. /**
  339. * Get a dictionary of unqualified simple registration arguments
  340. * representing this request.
  341. *
  342. * This method is essentially the inverse of
  343. * C{L{parseExtensionArgs}}. This method serializes the simple
  344. * registration request fields.
  345. */
  346. function getExtensionArgs()
  347. {
  348. $args = array();
  349. if ($this->required) {
  350. $args['required'] = implode(',', $this->required);
  351. }
  352. if ($this->optional) {
  353. $args['optional'] = implode(',', $this->optional);
  354. }
  355. if ($this->policy_url) {
  356. $args['policy_url'] = $this->policy_url;
  357. }
  358. return $args;
  359. }
  360. }
  361. /**
  362. * Represents the data returned in a simple registration response
  363. * inside of an OpenID C{id_res} response. This object will be created
  364. * by the OpenID server, added to the C{id_res} response object, and
  365. * then extracted from the C{id_res} message by the Consumer.
  366. *
  367. * @package OpenID
  368. */
  369. class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
  370. var $ns_alias = 'sreg';
  371. function Auth_OpenID_SRegResponse($data=null,
  372. $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
  373. {
  374. if ($data === null) {
  375. $this->data = array();
  376. } else {
  377. $this->data = $data;
  378. }
  379. $this->ns_uri = $sreg_ns_uri;
  380. }
  381. /**
  382. * Take a C{L{SRegRequest}} and a dictionary of simple
  383. * registration values and create a C{L{SRegResponse}} object
  384. * containing that data.
  385. *
  386. * request: The simple registration request object
  387. *
  388. * data: The simple registration data for this response, as a
  389. * dictionary from unqualified simple registration field name to
  390. * string (unicode) value. For instance, the nickname should be
  391. * stored under the key 'nickname'.
  392. */
  393. static function extractResponse($request, $data)
  394. {
  395. $obj = new Auth_OpenID_SRegResponse();
  396. $obj->ns_uri = $request->ns_uri;
  397. foreach ($request->allRequestedFields() as $field) {
  398. $value = Auth_OpenID::arrayGet($data, $field);
  399. if ($value !== null) {
  400. $obj->data[$field] = $value;
  401. }
  402. }
  403. return $obj;
  404. }
  405. /**
  406. * Create a C{L{SRegResponse}} object from a successful OpenID
  407. * library response
  408. * (C{L{openid.consumer.consumer.SuccessResponse}}) response
  409. * message
  410. *
  411. * success_response: A SuccessResponse from consumer.complete()
  412. *
  413. * signed_only: Whether to process only data that was
  414. * signed in the id_res message from the server.
  415. *
  416. * Returns a simple registration response containing the data that
  417. * was supplied with the C{id_res} response.
  418. */
  419. static function fromSuccessResponse($success_response, $signed_only=true)
  420. {
  421. global $Auth_OpenID_sreg_data_fields;
  422. $obj = new Auth_OpenID_SRegResponse();
  423. $obj->ns_uri = $obj->_getSRegNS($success_response->message);
  424. if ($signed_only) {
  425. $args = $success_response->getSignedNS($obj->ns_uri);
  426. } else {
  427. $args = $success_response->message->getArgs($obj->ns_uri);
  428. }
  429. if ($args === null || Auth_OpenID::isFailure($args)) {
  430. return null;
  431. }
  432. foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
  433. if (in_array($field_name, array_keys($args))) {
  434. $obj->data[$field_name] = $args[$field_name];
  435. }
  436. }
  437. return $obj;
  438. }
  439. function getExtensionArgs()
  440. {
  441. return $this->data;
  442. }
  443. // Read-only dictionary interface
  444. function get($field_name, $default=null)
  445. {
  446. if (!Auth_OpenID_checkFieldName($field_name)) {
  447. return null;
  448. }
  449. return Auth_OpenID::arrayGet($this->data, $field_name, $default);
  450. }
  451. function contents()
  452. {
  453. return $this->data;
  454. }
  455. }