1<?php
2/**
3 * This file is part of php-saml.
4 *
5 * (c) OneLogin Inc
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 *
10 * @package OneLogin
11 * @author  OneLogin Inc <saml-info@onelogin.com>
12 * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE
13 * @link    https://github.com/onelogin/php-saml
14 */
15
16namespace OneLogin\Saml2;
17
18/**
19 * SAML 2 Authentication Request
20 */
21class AuthnRequest
22{
23    /**
24     * Object that represents the setting info
25     *
26     * @var Settings
27     */
28    protected $_settings;
29
30    /**
31     * SAML AuthNRequest string
32     *
33     * @var string
34     */
35    private $_authnRequest;
36
37    /**
38     * SAML AuthNRequest ID.
39     *
40     * @var string
41     */
42    private $_id;
43
44    /**
45     * Constructs the AuthnRequest object.
46     *
47     * @param Settings $settings SAML Toolkit Settings
48     * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true'
49     * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true'
50     * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy
51     * @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated
52     */
53    public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true, $nameIdValueReq = null)
54    {
55        $this->_settings = $settings;
56
57        $spData = $this->_settings->getSPData();
58        $idpData = $this->_settings->getIdPData();
59        $security = $this->_settings->getSecurityData();
60
61        $id = Utils::generateUniqueID();
62        $issueInstant = Utils::parseTime2SAML(time());
63
64        $subjectStr = "";
65        if (isset($nameIdValueReq)) {
66            $subjectStr = <<<SUBJECT
67
68     <saml:Subject>
69        <saml:NameID Format="{$spData['NameIDFormat']}">{$nameIdValueReq}</saml:NameID>
70        <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></saml:SubjectConfirmation>
71    </saml:Subject>
72SUBJECT;
73        }
74
75        $nameIdPolicyStr = '';
76        if ($setNameIdPolicy) {
77            $nameIDPolicyFormat = $spData['NameIDFormat'];
78            if (isset($security['wantNameIdEncrypted']) && $security['wantNameIdEncrypted']) {
79                $nameIDPolicyFormat = Constants::NAMEID_ENCRYPTED;
80            }
81
82            $nameIdPolicyStr = <<<NAMEIDPOLICY
83
84    <samlp:NameIDPolicy
85        Format="{$nameIDPolicyFormat}"
86        AllowCreate="true" />
87NAMEIDPOLICY;
88        }
89
90
91        $providerNameStr = '';
92        $organizationData = $settings->getOrganization();
93        if (!empty($organizationData)) {
94            $langs = array_keys($organizationData);
95            if (in_array('en-US', $langs)) {
96                $lang = 'en-US';
97            } else {
98                $lang = $langs[0];
99            }
100            if (isset($organizationData[$lang]['displayname']) && !empty($organizationData[$lang]['displayname'])) {
101                $providerNameStr = <<<PROVIDERNAME
102    ProviderName="{$organizationData[$lang]['displayname']}"
103PROVIDERNAME;
104            }
105        }
106
107        $forceAuthnStr = '';
108        if ($forceAuthn) {
109            $forceAuthnStr = <<<FORCEAUTHN
110
111    ForceAuthn="true"
112FORCEAUTHN;
113        }
114
115        $isPassiveStr = '';
116        if ($isPassive) {
117            $isPassiveStr = <<<ISPASSIVE
118
119    IsPassive="true"
120ISPASSIVE;
121        }
122
123        $requestedAuthnStr = '';
124        if (isset($security['requestedAuthnContext']) && $security['requestedAuthnContext'] !== false) {
125            $authnComparison = 'exact';
126            if (isset($security['requestedAuthnContextComparison'])) {
127                $authnComparison = $security['requestedAuthnContextComparison'];
128            }
129
130            $authnComparisonAttr = '';
131            if (!empty($authnComparison)) {
132                $authnComparisonAttr = sprintf('Comparison="%s"', $authnComparison);
133            }
134
135            if ($security['requestedAuthnContext'] === true) {
136                $requestedAuthnStr = <<<REQUESTEDAUTHN
137
138    <samlp:RequestedAuthnContext $authnComparisonAttr>
139        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
140    </samlp:RequestedAuthnContext>
141REQUESTEDAUTHN;
142            } else {
143                $requestedAuthnStr .= "    <samlp:RequestedAuthnContext $authnComparisonAttr>\n";
144                foreach ($security['requestedAuthnContext'] as $contextValue) {
145                    $requestedAuthnStr .= "        <saml:AuthnContextClassRef>".$contextValue."</saml:AuthnContextClassRef>\n";
146                }
147                $requestedAuthnStr .= '    </samlp:RequestedAuthnContext>';
148            }
149        }
150
151        $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES);
152        $acsUrl = htmlspecialchars($spData['assertionConsumerService']['url'], ENT_QUOTES);
153        $request = <<<AUTHNREQUEST
154<samlp:AuthnRequest
155    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
156    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
157    ID="$id"
158    Version="2.0"
159{$providerNameStr}{$forceAuthnStr}{$isPassiveStr}
160    IssueInstant="$issueInstant"
161    Destination="{$idpData['singleSignOnService']['url']}"
162    ProtocolBinding="{$spData['assertionConsumerService']['binding']}"
163    AssertionConsumerServiceURL="{$acsUrl}">
164    <saml:Issuer>{$spEntityId}</saml:Issuer>{$subjectStr}{$nameIdPolicyStr}{$requestedAuthnStr}
165</samlp:AuthnRequest>
166AUTHNREQUEST;
167
168        $this->_id = $id;
169        $this->_authnRequest = $request;
170    }
171
172    /**
173     * Returns deflated, base64 encoded, unsigned AuthnRequest.
174     *
175     * @param bool|null $deflate Whether or not we should 'gzdeflate' the request body before we return it.
176     *
177     * @return string
178     */
179    public function getRequest($deflate = null)
180    {
181        $subject = $this->_authnRequest;
182
183        if (is_null($deflate)) {
184            $deflate = $this->_settings->shouldCompressRequests();
185        }
186
187        if ($deflate) {
188            $subject = gzdeflate($this->_authnRequest);
189        }
190
191        $base64Request = base64_encode($subject);
192        return $base64Request;
193    }
194
195    /**
196     * Returns the AuthNRequest ID.
197     *
198     * @return string
199     */
200    public function getId()
201    {
202        return $this->_id;
203    }
204
205    /**
206     * Returns the XML that will be sent as part of the request
207     *
208     * @return string
209     */
210    public function getXML()
211    {
212        return $this->_authnRequest;
213    }
214}
215