1 | <?php |
||
2 | |||
3 | namespace SilverStripe\RealMe\Model; |
||
4 | |||
5 | use DOMDocument; |
||
6 | use DOMXPath; |
||
7 | |||
8 | use SilverStripe\ORM\FieldType\DBField; |
||
9 | use SilverStripe\View\ViewableData; |
||
10 | use SilverStripe\ORM\FieldType\DBDatetime; |
||
11 | |||
12 | /** |
||
13 | * Class RealMeFederatedIdentity |
||
14 | * |
||
15 | * Contains data to describe an identity, verified by RealMe. Provides simpler access to identity information, rather |
||
16 | * than having to parse XML via {@link DOMDocument} or similar. |
||
17 | * |
||
18 | * All public methods return individual elements from the federated identity. |
||
19 | * |
||
20 | * Standard usage: |
||
21 | * Injector::inst()->get('RealMeService')->enforceLogin(); // Enforce login and ensure auth data exists |
||
22 | * $identity = Injector::inst()->get('RealMeService')->getAuthData()->getIdentity(); |
||
23 | * |
||
24 | * Notes: |
||
25 | * - We can't store the original DOMDocument as it's not possible to properly serialize and unserialize this such that |
||
26 | * it can be stored in session. Therefore, during object instantiation, we parse the XML, and store individual details |
||
27 | * directly against properties. |
||
28 | * |
||
29 | * - See this object's constructor for the XML / DOMDocument object expected to be passed during instantiation. |
||
30 | */ |
||
31 | class FederatedIdentity extends ViewableData |
||
32 | { |
||
33 | const SOURCE_XML = 'urn:nzl:govt:ict:stds:authn:safeb64:attribute:igovt:IVS:Assertion:Identity'; |
||
34 | const SOURCE_JSON = 'urn:nzl:govt:ict:stds:authn:safeb64:attribute:igovt:IVS:Assertion:JSON:Identity'; |
||
35 | |||
36 | /** |
||
37 | * @var string The FIT (Federated Identity Tag) for this identity. This is the unique string that identifies an |
||
38 | * individual, and should generally be mapped one-to-one with a {@link Member} object |
||
39 | */ |
||
40 | private $nameId; |
||
41 | |||
42 | /** |
||
43 | * @var string The given first name(s) of the federated identity returned by RealMe. |
||
44 | */ |
||
45 | public $FirstName; |
||
46 | |||
47 | /** |
||
48 | * @var string The given middle name(s) of the federated identity returned by RealMe. |
||
49 | */ |
||
50 | public $MiddleName; |
||
51 | |||
52 | /** |
||
53 | * @var string The given last name of the federated identity returned by RealMe. |
||
54 | */ |
||
55 | public $LastName; |
||
56 | |||
57 | /** |
||
58 | * @var string The gender of the federated identity returned by RealMe. Will be one of 'M', 'F', possibly 'U' or 'O' |
||
59 | * (messaging specs are unclear). |
||
60 | */ |
||
61 | public $Gender; |
||
62 | |||
63 | /** |
||
64 | * @var DOMNodeList Undocumented in RealMe messaging spec, generally describes the quality of birth info based |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
65 | * presumably on the source. |
||
66 | */ |
||
67 | public $BirthInfoQuality; |
||
68 | |||
69 | /** |
||
70 | * @var string The birth year of the federated identity returned by RealMe, e.g. 1993, 1954, 2015. |
||
71 | * Probably better to use {@link getDateOfBirth()} which will return an {@link SS_Datetime} object. |
||
72 | */ |
||
73 | public $BirthYear; |
||
74 | |||
75 | /** |
||
76 | * @var string The birth month of the federated identity returned by RealMe, e.g. 05 (May). |
||
77 | * Probably better to use {@link getDateOfBirth()} which will return an {@link SS_Datetime} object. |
||
78 | */ |
||
79 | public $BirthMonth; |
||
80 | |||
81 | /** |
||
82 | * @var string The birth day of the federated identity returned by RealMe, e.g. 05 (5th day of the month). |
||
83 | * Probably better to use {@link getDateOfBirth()} which will return an {@link SS_Datetime} object. |
||
84 | */ |
||
85 | public $BirthDay; |
||
86 | |||
87 | /** |
||
88 | * @var string Undocumented in RealMe messaging spec, generally describes the quality of birthplace info based |
||
89 | * presumably on the source. |
||
90 | */ |
||
91 | public $BirthPlaceQuality; |
||
92 | |||
93 | /** |
||
94 | * @var string The country of birth for the given federated identity returned by RealMe. |
||
95 | */ |
||
96 | public $BirthPlaceCountry; |
||
97 | |||
98 | /** |
||
99 | * @var string The birthplace 'locality' of the federated identity returned by RealMe, e.g. 'Wellington', 'Unknown' |
||
100 | */ |
||
101 | public $BirthPlaceLocality; |
||
102 | |||
103 | public function __construct($nameId) |
||
104 | { |
||
105 | parent::__construct(); |
||
106 | $this->nameId = $nameId; |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Constructor that sets the expected federated identity details based on a provided DOMDocument. The expected XML |
||
111 | * structure for the DOMDocument is the following: |
||
112 | * |
||
113 | * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
||
114 | * <ns1:Party |
||
115 | * xmlns:ns1="urn:oasis:names:tc:ciq:xpil:3" |
||
116 | * xmlns:ns2="urn:oasis:names:tc:ciq:ct:3" |
||
117 | * xmlns:ns3="urn:oasis:names:tc:ciq:xnl:3" |
||
118 | * xmlns:ns4="http://www.w3.org/1999/xlink" |
||
119 | * xmlns:ns5="urn:oasis:names:tc:ciq:xal:3"> |
||
120 | * <ns1:PartyName> |
||
121 | * <ns3:PersonName> |
||
122 | * <ns3:NameElement ns3:ElementType="FirstName">Edmund</ns3:NameElement> |
||
123 | * <ns3:NameElement ns3:ElementType="MiddleName">Percival</ns3:NameElement> |
||
124 | * <ns3:NameElement ns3:ElementType="LastName">Hillary</ns3:NameElement> |
||
125 | * </ns3:PersonName> |
||
126 | * </ns1:PartyName> |
||
127 | * <ns1:PersonInfo ns1:Gender="M"/> |
||
128 | * <ns1:BirthInfo ns2:DataQualityType="Valid"> |
||
129 | * <ns1:BirthInfoElement ns1:Type="BirthYear">1919</ns1:BirthInfoElement> |
||
130 | * <ns1:BirthInfoElement ns1:Type="BirthMonth">07</ns1:BirthInfoElement> |
||
131 | * <ns1:BirthInfoElement ns1:Type="BirthDay">20</ns1:BirthInfoElement> |
||
132 | * <ns1:BirthPlaceDetails ns2:DataQualityType="Valid"> |
||
133 | * <ns5:Country> |
||
134 | * <ns5:NameElement ns5:NameType="Name">New Zealand</ns5:NameElement> |
||
135 | * </ns5:Country> |
||
136 | * <ns5:Locality> |
||
137 | * <ns5:NameElement ns5:NameType="Name">Auckland</ns5:NameElement> |
||
138 | * </ns5:Locality> |
||
139 | * </ns1:BirthPlaceDetails> |
||
140 | * </ns1:BirthInfo> |
||
141 | * </ns1:Party> |
||
142 | * |
||
143 | * @param DOMDocument $identity |
||
144 | * @param string $nameId |
||
145 | */ |
||
146 | public static function createFromXML(DOMDocument $identityDocument, $nameId) |
||
147 | { |
||
148 | $identity = new self($nameId); |
||
149 | |||
150 | $xpath = new DOMXPath($identityDocument); |
||
151 | $xpath->registerNamespace('p', 'urn:oasis:names:tc:ciq:xpil:3'); |
||
152 | $xpath->registerNamespace('dataQuality', 'urn:oasis:names:tc:ciq:ct:3'); |
||
153 | $xpath->registerNamespace('n', 'urn:oasis:names:tc:ciq:xnl:3'); |
||
154 | $xpath->registerNamespace('xlink', 'http://www.w3.org/1999/xlink'); |
||
155 | $xpath->registerNamespace('addr', 'urn:oasis:names:tc:ciq:xal:3'); |
||
156 | |||
157 | // Name elements |
||
158 | $identity->FirstName = self::getNodeValue( |
||
159 | $xpath, |
||
160 | "/p:Party/p:PartyName/n:PersonName/n:NameElement[@n:ElementType='FirstName']" |
||
161 | ); |
||
162 | $identity->MiddleName = self::getNodeValue( |
||
163 | $xpath, |
||
164 | "/p:Party/p:PartyName/n:PersonName/n:NameElement[@n:ElementType='MiddleName']" |
||
165 | ); |
||
166 | $identity->LastName = self::getNodeValue( |
||
167 | $xpath, |
||
168 | "/p:Party/p:PartyName/n:PersonName/n:NameElement[@n:ElementType='LastName']" |
||
169 | ); |
||
170 | |||
171 | // Gender |
||
172 | $identity->Gender = self::getNamedItemNodeValue($xpath, '/p:Party/p:PersonInfo[@p:Gender]', 'Gender'); |
||
173 | |||
174 | // Birth info |
||
175 | $identity->BirthInfoQuality = $xpath->query("/p:Party/p:BirthInfo[@dataQuality:DataQualityType]"); |
||
0 ignored issues
–
show
It seems like
$xpath->query('/p:Party/...lity:DataQualityType]') of type DOMNodeList or false is incompatible with the declared type SilverStripe\RealMe\Model\DOMNodeList of property $BirthInfoQuality .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
176 | |||
177 | // Birth date |
||
178 | $identity->BirthYear = self::getNodeValue( |
||
179 | $xpath, |
||
180 | "/p:Party/p:BirthInfo/p:BirthInfoElement[@p:Type='BirthYear']" |
||
181 | ); |
||
182 | $identity->BirthMonth = self::getNodeValue( |
||
183 | $xpath, |
||
184 | "/p:Party/p:BirthInfo/p:BirthInfoElement[@p:Type='BirthMonth']" |
||
185 | ); |
||
186 | $identity->BirthDay = self::getNodeValue( |
||
187 | $xpath, |
||
188 | "/p:Party/p:BirthInfo/p:BirthInfoElement[@p:Type='BirthDay']" |
||
189 | ); |
||
190 | |||
191 | // Birth place |
||
192 | $identity->BirthPlaceQuality = self::getNamedItemNodeValue( |
||
193 | $xpath, |
||
194 | '/p:Party/p:BirthInfo/p:BirthPlaceDetails[@dataQuality:DataQualityType]', |
||
195 | 'DataQualityType' |
||
196 | ); |
||
197 | $identity->BirthPlaceCountry = self::getNodeValue( |
||
198 | $xpath, |
||
199 | "/p:Party/p:BirthInfo/p:BirthPlaceDetails/addr:Country/addr:NameElement[@addr:NameType='Name']" |
||
200 | ); |
||
201 | $identity->BirthPlaceLocality = self::getNodeValue( |
||
202 | $xpath, |
||
203 | "/p:Party/p:BirthInfo/p:BirthPlaceDetails/addr:Locality/addr:NameElement[@addr:NameType='Name']" |
||
204 | ); |
||
205 | |||
206 | return $identity; |
||
207 | } |
||
208 | |||
209 | public function isValid() |
||
210 | { |
||
211 | return true; |
||
212 | } |
||
213 | |||
214 | public function getDateOfBirth() |
||
215 | { |
||
216 | if ($this->BirthYear && $this->BirthMonth && $this->BirthDay) { |
||
217 | $value = sprintf('%d-%d-%d', $this->BirthYear, $this->BirthMonth, $this->BirthDay); |
||
218 | $dateTime = DBDatetime::create()->setValue($value); |
||
219 | return $dateTime; |
||
220 | } else { |
||
221 | return null; |
||
222 | } |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * @param DOMXPath $xpath The DOMXPath object to carry out the query on |
||
227 | * @param string $query The XPath query to find the relevant node |
||
228 | * @param string $namedAttr The named attribute to retrieve from the XPath query |
||
229 | * @return string|null Either the value from the named item, or null if no item exists |
||
230 | */ |
||
231 | private static function getNamedItemNodeValue(DOMXPath $xpath, $query, $namedAttr) |
||
232 | { |
||
233 | $query = $xpath->query($query); |
||
234 | $value = null; |
||
235 | |||
236 | if ($query->length > 0) { |
||
237 | $item = $query->item(0); |
||
238 | |||
239 | if ($item->hasAttributes()) { |
||
240 | $value = $item->attributes->getNamedItem($namedAttr); |
||
241 | |||
242 | if (strlen($value->nodeValue) > 0) { |
||
243 | $value = $value->nodeValue; |
||
244 | } |
||
245 | } |
||
246 | } |
||
247 | |||
248 | return $value; |
||
0 ignored issues
–
show
|
|||
249 | } |
||
250 | |||
251 | /** |
||
252 | * @param DOMXPath $xpath The DOMXPath object to carry out the query on |
||
253 | * @param string $query The XPath query to find the relevant node |
||
254 | * @return string|null Either the first matching node's value (there should only ever be one), or null if none found |
||
255 | */ |
||
256 | private static function getNodeValue(DOMXPath $xpath, $query) |
||
257 | { |
||
258 | $query = $xpath->query($query); |
||
259 | return ($query->length > 0 ? $query->item(0)->nodeValue : null); |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * create a FederatedIdentity from a JSON string. |
||
264 | * |
||
265 | * @param string $identityHashMap |
||
266 | * @param string $nameId |
||
267 | * @return void |
||
268 | */ |
||
269 | public static function createFromJSON($identityHashMap, $nameId) |
||
270 | { |
||
271 | $identity = new self($nameId); |
||
272 | |||
273 | $identityMap = json_decode($identityHashMap, true); |
||
274 | |||
275 | // Name elements |
||
276 | $identity->FirstName = $identityMap['name']['firstName']; |
||
277 | $identity->MiddleName = $identityMap['name']['middleName']; |
||
278 | $identity->LastName = $identityMap['name']['lastName']; |
||
279 | |||
280 | // Gender |
||
281 | $identity->Gender = $identityMap['gender']['genderValue']; |
||
282 | |||
283 | // Birth info |
||
284 | $identity->BirthInfoQuality = $identityMap['dateOfBirth']['dateOfBirthDisputed'] ?: 'Valid'; |
||
0 ignored issues
–
show
It seems like
$identityMap['dateOfBirt...thDisputed'] ?: 'Valid' can also be of type string . However, the property $BirthInfoQuality is declared as type SilverStripe\RealMe\Model\DOMNodeList . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
285 | |||
286 | // Birth date |
||
287 | $identity->BirthYear = explode('-', $identityMap['dateOfBirth']['dateOfBirthValue'])[0]; |
||
288 | $identity->BirthMonth = explode('-', $identityMap['dateOfBirth']['dateOfBirthValue'])[1]; |
||
289 | $identity->BirthDay = explode('-', $identityMap['dateOfBirth']['dateOfBirthValue'])[2]; |
||
290 | |||
291 | // Birth place |
||
292 | $identity->BirthPlaceQuality = $identityMap['placeOfBirth']['placeOfBirthDisputed']?: 'Valid'; |
||
293 | $identity->BirthPlaceCountry = $identityMap['placeOfBirth']['country']; |
||
294 | $identity->BirthPlaceLocality = $identityMap['placeOfBirth']['locality']; |
||
295 | |||
296 | return $identity; |
||
0 ignored issues
–
show
|
|||
297 | } |
||
298 | } |
||
299 |