1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2014-2025
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: */
22:
23: namespace SimpleID\Protocols\OpenID;
24:
25: use SimpleID\Util\ArrayWrapper;
26:
27: /**
28: * An abstract class representing an OpenID message (request
29: * and response).
30: *
31: * This class is a subclass of {@link ArrayWrapper}. Message parameters
32: * are stored in {@link ArrayWrapper->container} and are accessed
33: * using array syntax.
34: */
35: abstract class Message extends ArrayWrapper {
36:
37: const OPENID_VERSION_2 = 2;
38: const OPENID_VERSION_1_1 = 1;
39:
40: /** Constant for OpenID namespace */
41: const OPENID_NS_2_0 = 'http://specs.openid.net/auth/2.0';
42:
43: /**
44: * A mapping of Type URIs of OpenID extnesions to aliases provided in an OpenID
45: * message.
46: *
47: * @var array<string, string>
48: */
49: protected $extension_map = [ "http://openid.net/extensions/sreg/1.1" => "sreg" ]; // For sreg 1.0 compatibility
50:
51: /**
52: * The version of the OpenID specification associated with
53: * the current OpenID message. This can be either {@link OPENID_VERSION_1_1}
54: * or {@link OPENID_VERSION_2}.
55: *
56: * @var int
57: */
58: protected $version;
59:
60: /**
61: * Returns the OpenID version of the message
62: *
63: * @return int either OPENID_VERSION_2 or OPENID_VERSION_1_1
64: */
65: public function getVersion() {
66: return $this->version;
67: }
68:
69: /**
70: * Filters an OpenID request to find keys specific to an extension, as specified
71: * by the Type URI.
72: *
73: * For exmaple, if the extension has the Type URI http://example.com/ and the
74: * alias example, this function will return an array of all the keys in the
75: * OpenID request which starts with openid.example
76: *
77: * @param string $ns the Type URI of the extension
78: * @return array<string, string> the filtered request, with the prefix (in the example above,
79: * openid.example.) stripped in the keys.
80: */
81: public function getParamsForExtension($ns) {
82: if (!isset($this->extension_map[$ns])) return [];
83:
84: $prefix = $this->getPrefix();
85: $alias = $this->extension_map[$ns];
86: $return = [];
87:
88: if (is_array($this->container)) {
89: foreach ($this->container as $key => $value) {
90: if ($key == $prefix . $alias) {
91: $return['#default'] = $value;
92: }
93: if (strpos($key, $prefix . $alias . '.') === 0) {
94: $return[substr($key, strlen($prefix . $alias . '.'))] = $value;
95: }
96: }
97: }
98:
99: return $return;
100: }
101:
102: /**
103: * Determines whether an extension is present in an OpenID request.
104: *
105: * @param string $ns the Type URI of the extension
106: * @return bool true if the extension is present in the request
107: */
108: public function hasExtension($ns) {
109: if (!isset($this->extension_map[$ns])) return false;
110:
111: $prefix = $this->getPrefix();
112: $alias = $this->extension_map[$ns];
113:
114: if (is_array($this->container)) {
115: foreach ($this->container as $key => $value) {
116: if ((strpos($key, $prefix . $alias . '.') === 0) || (strpos($key, $prefix . $alias . '=') === 0)) {
117: return true;
118: }
119: }
120: }
121:
122: return false;
123: }
124:
125: /**
126: * Obtains the mapping between namespace URIs and their aliases.
127: *
128: * @return array<string, string> the mapping between namespace URIs and their aliases
129: */
130: public function getExtensionMap() {
131: return $this->extension_map;
132: }
133:
134: /**
135: * Creates a OpenID message for direct response.
136: *
137: * The response will be encoded using Key-Value Form Encoding.
138: *
139: * @param array<string, string> $data the data in the response
140: * @return string|null the message in key-value form encoding
141: * @link http://openid.net/specs/openid-authentication-1_1.html#anchor32, http://openid.net/specs/openid-authentication-2_0.html#kvform
142: */
143: static protected function toKeyValueForm($data) {
144: $message = '';
145:
146: foreach ($data as $key => $value) {
147: // Filter out invalid characters
148: if (strpos($key, ':') !== false) return null;
149: if (strpos($key, "\n") !== false) return null;
150: if (strpos($value, "\n") !== false) return null;
151:
152: $message .= "$key:$value\n";
153: }
154: return $message;
155: }
156:
157: /**
158: * Calculates the base string from which an OpenID signature is generated,
159: * given a list of fields to sign.
160: *
161: * @param array<string> $signed_fields the list of fields to sign
162: * @param string $prefix the prefix to be prepended to $signed_field to obtain
163: * the field value - used for Requests
164: * @return string the signature base string
165: * @link http://openid.net/specs/openid-authentication-2_0.html#anchor11
166: */
167: protected function buildSignatureBaseString($signed_fields, $prefix = '') {
168: $signed_data = [];
169: // Remove duplicates
170: $signed_fields = array_keys(array_flip($signed_fields));
171:
172: foreach ($signed_fields as $field) {
173: $key = $prefix . $field;
174: if (array_key_exists($key, $this->container)) {
175: $signed_data[$field] = $this->container[$key];
176: }
177: }
178:
179: return self::toKeyValueForm($signed_data);
180: }
181:
182: /**
183: * Calculates the base string from which an OpenID signature is generated.
184: *
185: * Subclasses specify the list of fields to sign and calls {@link buildSignatureBaseString()}
186: *
187: * @return string the signature base string
188: * @link http://openid.net/specs/openid-authentication-2_0.html#anchor11
189: */
190: public abstract function getSignatureBaseString();
191:
192: /**
193: * Returns the base string from which an OpenID signature is generated
194: *
195: * @return string the base string
196: */
197: protected abstract function getPrefix();
198: }
199: ?>