silverstripe /
silverstripe-realme
| 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
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
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
|
|||
| 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;
}
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
|
|||
| 297 | } |
||
| 298 | } |
||
| 299 |