Issues (10)

src/SamlpExtensions.php (1 issue)

1
<?php
2
3
namespace OMSAML2;
4
5
use DOMElement;
6
use InvalidArgumentException;
7
use OMSAML2\Chunks\EidasRequestedAttribute;
8
use OMSAML2\Chunks\EidasRequestedAttributes;
9
use OMSAML2\Chunks\EidasSPType;
10
use SAML2\DOMDocumentFactory;
11
use SAML2\Utils;
12
use SAML2\XML\Chunk;
13
14
/**
15
 * Class implementing samlp(urn:oasis:names:tc:SAML:2.0:protocol):Extensions with (http://eidas.europa.eu/saml-extensions)
16
 * eidas:SPType and eidas:RequestedAttributes, for simplesamlphp/saml2 library
17
 *
18
 * @package OMSAML2
19
 */
20
class SamlpExtensions extends Chunk
21
{
22
    const NAME_FORMAT_URI = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
23
24
    public static $ATTR_PERSON_IDENTIFIER = [
25
        'Name' => 'http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier'
26
    ];
27
    public static $ATTR_CURRENT_GIVEN_NAME = [
28
        'Name' => 'http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName'
29
    ];
30
    public static $ATTR_CURRENT_FAMILY_NAME = [
31
        'Name' => 'http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName'
32
    ];
33
    public static $ATTR_CURRENT_ADDRESS = [
34
        'Name' => 'http://eidas.europa.eu/attributes/naturalperson/CurrentAddress'
35
    ];
36
    public static $ATTR_DATE_OF_BIRTH = [
37
        'Name' => 'http://eidas.europa.eu/attributes/naturalperson/DateOfBirth'
38
    ];
39
    public static $ATTR_PLACE_OF_BIRTH = [
40
        'Name' => 'http://eidas.europa.eu/attributes/naturalperson/PlaceOfBirth'
41
    ];
42
    public static $ATTR_COUNTRY_CODE_OF_BIRTH = [
43
        'Name' => 'http://www.stork.gov.eu/1.0/countryCodeOfBirth'
44
    ];
45
    public static $ATTR_EMAIL = [
46
        'Name' => 'http://www.stork.gov.eu/1.0/eMail'
47
    ];
48
    public static $ATTR_AGE = [
49
        'Name' => 'http://www.stork.gov.eu/1.0/age'
50
    ];
51
    public static $ATTR_IS_AGE_OVER_18 = [
52
        'Name' => 'http://www.stork.gov.eu/1.0/isAgeOver',
53
        'AttributeValue' => 18
54
    ];
55
    public static $ATTR_CZMORIS_PHONE_NUMBER = [
56
        'Name' => 'http://schemas.eidentity.cz/moris/2016/identity/claims/phonenumber'
57
    ];
58
    public static $ATTR_CZMORIS_TR_ADRESA_ID = [
59
        'Name' => 'http://schemas.eidentita.cz/moris/2016/identity/claims/tradresaid'
60
    ];
61
    public static $ATTR_CZMORIS_ID_TYPE = [
62
        'Name' => 'http://schemas.eidentita.cz/moris/2016/identity/claims/idtype'
63
    ];
64
    public static $ATTR_CZMORIS_ID_NUMBER = [
65
        'Name' => 'http://schemas.eidentita.cz/moris/2016/identity/claims/idnumber'
66
    ];
67
68
    /**@var EidasSPType $sptype */
69
    private $sptype = null;
70
    /**@var $requested_attributes EidasRequestedAttribute[] */
0 ignored issues
show
Documentation Bug introduced by
The doc comment $requested_attributes at position 0 could not be parsed: Unknown type name '$requested_attributes' at position 0 in $requested_attributes.
Loading history...
71
    private $requested_attributes = [];
72
73
    /**
74
     * Constructor will parse DOMElement containing samlp:Extensions for known attributes (RequestedAttributes, RequestedAttribute and SPType)
75
     *
76
     * @param DOMElement $dom
77
     * @throws InvalidArgumentException
78
     */
79
    public function __construct(?DOMelement $dom = null)
80
    {
81
        if ($dom === null) {
82
            $this->sptype = new EidasSPType();
83
            return;
84
        }
85
        parent::__construct($dom);
86
        $this->sptype = new EidasSPType($dom->getElementsByTagNameNS(EidasSPType::NS_EIDAS, EidasSPType::LOCAL_NAME)->item(0));
87
        $this->requested_attributes = (new EidasRequestedAttributes($dom))->requested_attributes;
88
    }
89
90
    public function addRequestedAttributeParams(string $Name, ?string $NameFormat = self::NAME_FORMAT_URI, bool $isRequired = false, $AttributeValue = null): SamlpExtensions
91
    {
92
        return $this->addRequestedAttribute([
93
            'Name' => $Name,
94
            'NameFormat' => $NameFormat,
95
            'isRequired' => $isRequired,
96
            'AttributeValue' => $AttributeValue
97
        ]);
98
    }
99
100
    /**
101
     * Adds requested attribute by single array definition, array must contain only 'Name' key with string value, all other keys are optional
102
     * Defaults are NameFormat=${NAME_FORMAT_URI} isRequired=false and no AttributeValue
103
     *
104
     * @param array $attribute
105
     * @return SamlpExtensions
106
     * @throws InvalidArgumentException if any required argument is missing or if provided argument type is invalid
107
     */
108
    public function addRequestedAttribute(array $attribute): SamlpExtensions
109
    {
110
        $requestedAttribute = new EidasRequestedAttribute();
111
112
        if (empty($attribute['Name'])) {
113
            throw new InvalidArgumentException("Required attribute Name is missing");
114
        } else if (!is_string($attribute['Name'])) {
115
            throw new InvalidArgumentException("Attribute Name must be string");
116
        } else {
117
            $requestedAttribute->Name = $attribute['Name'];
118
        }
119
        if (!empty($attribute['NameFormat']) && !is_string($attribute['NameFormat'])) {
120
            throw new InvalidArgumentException("Attribute NameFormat must be string");
121
        }
122
        if (!empty($attribute['isRequired']) && !is_bool($attribute['isRequired'])) {
123
            throw new InvalidArgumentException("Attribute isRequired must be boolean");
124
        }
125
        if (!empty($attribute['AttributeValue'])) {
126
            if (!is_scalar($attribute['AttributeValue'])) {
127
                throw new InvalidArgumentException("AttributeValue should be primitive type, such as string, number or boolean");
128
            }
129
130
            $requestedAttribute->NodeValue = $attribute['AttributeValue'];
131
        }
132
133
        // set default values for not-defined attributes
134
        if (!empty($attribute['NameFormat'])) {
135
            $requestedAttribute->NameFormat = $attribute['NameFormat'];
136
        }
137
        if (!empty($attribute['isRequired'])) {
138
            $requestedAttribute->isRequired = $attribute['isRequired'];
139
        }
140
141
        // add to queue
142
        $this->requested_attributes[] = $requestedAttribute;
143
144
        // return $this for chaining
145
        return $this;
146
    }
147
148
    public function getSPType(): string
149
    {
150
        return $this->sptype->sptype;
151
    }
152
153
    /**
154
     * Allowed values for SPType (Service Provider Type) are "public" and "private",
155
     * invalid values will be replaced by default value "public"
156
     *
157
     * @param string $sptype
158
     * @return SamlpExtensions
159
     */
160
    public function setSPType(string $sptype): SamlpExtensions
161
    {
162
        $this->sptype->sptype = in_array($sptype, ['public', 'private']) ? $sptype : 'public';
163
        return $this;
164
    }
165
166
    /**
167
     * Adds all pre-defined attributes (from ${getAllDefaultAttributes}) to attributes,
168
     * that should be added into DOMElement later (using ${toXML})
169
     *
170
     * @return SamlpExtensions
171
     * @see toXML
172
     * @see getAllDefaultAttributes
173
     */
174
    public function addAllDefaultAttributes(): SamlpExtensions
175
    {
176
        foreach ($this->getAllDefaultAttributes() as $attrArray) {
177
            $this->addRequestedAttribute($attrArray);
178
        }
179
        return $this;
180
    }
181
182
    /**
183
     * Returns array of all pre-defined attributes,
184
     * each attribute as an array compatible with this class method ${addRequestedAttribute}
185
     *
186
     * @return array
187
     * @see addRequestedAttribute
188
     */
189
    public function getAllDefaultAttributes(): array
190
    {
191
        return [
192
            self::$ATTR_AGE,
193
            self::$ATTR_COUNTRY_CODE_OF_BIRTH,
194
            self::$ATTR_CURRENT_ADDRESS,
195
            self::$ATTR_CURRENT_FAMILY_NAME,
196
            self::$ATTR_CURRENT_GIVEN_NAME,
197
            self::$ATTR_DATE_OF_BIRTH,
198
            self::$ATTR_EMAIL,
199
            self::$ATTR_IS_AGE_OVER_18,
200
            self::$ATTR_PERSON_IDENTIFIER,
201
            self::$ATTR_CZMORIS_ID_NUMBER,
202
            self::$ATTR_CZMORIS_ID_TYPE,
203
            self::$ATTR_CZMORIS_TR_ADRESA_ID,
204
            self::$ATTR_CZMORIS_PHONE_NUMBER,
205
            self::$ATTR_PLACE_OF_BIRTH
206
        ];
207
    }
208
209
    public function toXML(DOMElement $parent): DOMElement
210
    {
211
        // will throw TypeError on empty or non-compatible $this->dom value
212
        $dom = Utils::copyElement($parent);
213
        $doc = $dom->ownerDocument;
214
215
        $extensions = $doc->createElementNS('urn:oasis:names:tc:SAML:2.0:protocol', 'samlp:Extensions');
216
217
        $this->sptype->toXML($extensions);
218
219
        // set eidas:RequestedAttributes if any defined
220
        if (!empty($this->getRequestedAttributes())) {
221
222
            $requested_attributes = new EidasRequestedAttributes();
223
            $requested_attributes->requested_attributes = $this->requested_attributes;
224
            $requested_attributes->toXML($extensions);
225
        }
226
227
        $dom->appendChild($extensions);
228
229
        return DOMDocumentFactory::fromString($dom->ownerDocument->saveXML($dom))->documentElement;
230
    }
231
232
    /**
233
     * Return currently queued RequestedAttributes in form of array configuration
234
     *
235
     * @return EidasRequestedAttribute[]
236
     */
237
    public function getRequestedAttributes(): array
238
    {
239
        return $this->requested_attributes;
240
    }
241
242
    /**
243
     * Removes all queued RequestedAttributes
244
     *
245
     * @return SamlpExtensions
246
     */
247
    public function removeAllRequestedAttributes(): SamlpExtensions
248
    {
249
        $this->requested_attributes = [];
250
        return $this;
251
    }
252
}