Issues (37)

src/Model/FederatedIdentity.php (5 issues)

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
The type SilverStripe\RealMe\Model\DOMNodeList was not found. Did you mean DOMNodeList? If so, make sure to prefix the type with \.
Loading history...
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
Documentation Bug introduced by
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..

Loading history...
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
Bug Best Practice introduced by
The expression return $value also could return the type DOMNode which is incompatible with the documented return type null|string.
Loading history...
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
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
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
Bug Best Practice introduced by
The expression return $identity returns the type SilverStripe\RealMe\Model\FederatedIdentity which is incompatible with the documented return type void.
Loading history...
297
    }
298
}
299