├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── public
└── .gitkeep
├── src
├── Aacotroneo
│ └── Saml2
│ │ ├── Events
│ │ ├── Saml2Event.php
│ │ ├── Saml2LoginEvent.php
│ │ └── Saml2LogoutEvent.php
│ │ ├── Http
│ │ └── Controllers
│ │ │ └── Saml2Controller.php
│ │ ├── Saml2Auth.php
│ │ ├── Saml2ServiceProvider.php
│ │ └── Saml2User.php
├── config
│ ├── mytestidp1_idp_settings.php
│ └── saml2_settings.php
└── routes.php
└── tests
├── .gitkeep
└── Saml2
├── Saml2AuthServiceProviderTest.php
└── Saml2AuthTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.6
5 | - 7.0
6 | - 7.1
7 | - 7.2
8 |
9 |
10 | before_script:
11 | - travis_retry composer self-update
12 | - travis_retry composer install --prefer-source --no-interaction
13 |
14 | script: vendor/bin/phpunit
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Alejandro Cotroneo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # *Status*: Not Active
2 | This project is no longer maintained. I'd be glad to transfer ownership, or otherwise you can easily replace it by some of the many forks (let me know if someone wants to list theirs here, or some oher library). The library itself shouldn't change much, but there are occational changes needed to keep up with Laravel and PHP version updates
3 | * https://github.com/24Slides/laravel-saml2
4 |
5 | ## Laravel 5 - Saml2
6 |
7 | [](https://travis-ci.org/aacotroneo/laravel-saml2)
8 |
9 | A Laravel package for Saml2 integration as a SP (service provider) based on [OneLogin](https://github.com/onelogin/php-saml) toolkit, which is much lighter and easier to install than simplesamlphp SP. It doesn't need separate routes or session storage to work!
10 |
11 | The aim of this library is to be as simple as possible. We won't mess with Laravel users, auth, session... We prefer to limit ourselves to a concrete task. Ask the user to authenticate at the IDP and process the response. Same case for SLO (Single Logout) requests.
12 |
13 | ## Installation - Composer
14 |
15 | You can install the package via composer:
16 |
17 | ```
18 | composer require aacotroneo/laravel-saml2
19 | ```
20 | Or manually add this to your composer.json:
21 |
22 | **composer.json**
23 | ```json
24 | "aacotroneo/laravel-saml2": "*"
25 | ```
26 |
27 | If you are using Laravel 5.5 and up, the service provider will automatically get registered.
28 |
29 | For older versions of Laravel (<5.5), you have to add the service provider:
30 |
31 | **config/app.php**
32 | ```php
33 | 'providers' => [
34 | ...
35 | Aacotroneo\Saml2\Saml2ServiceProvider::class,
36 | ]
37 | ```
38 |
39 | Then publish the config files with `php artisan vendor:publish --provider="Aacotroneo\Saml2\Saml2ServiceProvider"`. This will add the files `app/config/saml2_settings.php` & `app/config/saml2/mytestidp1_idp_settings.php`, which you will need to customize.
40 |
41 | The `mytestidp1_idp_settings.php` config is handled almost directly by [OneLogin](https://github.com/onelogin/php-saml) so you should refer to that for full details, but we'll cover here what's really necessary. There are some other config about routes you may want to check, they are pretty strightforward.
42 |
43 | ### Configuration
44 |
45 | #### Define the IDPs
46 | Define names of all the IDPs you want to configure in `saml2_settings.php`. Optionally keep `mytestidp1` (case-sensitive) as the first IDP if you want to use the simplesamlphp demo, and add real IDPs after that. The name of the IDP will show up in the URL used by the Saml2 routes this library makes, as well as internally in the filename for each IDP's config.
47 |
48 | **config/saml2_settings.php**
49 | ```php
50 | 'idpNames' => ['mytestidp1', 'test', 'myidp2'],
51 | ```
52 |
53 | #### Configure laravel-saml2 to know about each IDP
54 |
55 | You will need to create a separate configuration file for each IDP under `app/config/saml2/` folder. e.g. `test_idp_settings.php`. You can use `mytestidp1_idp_settings.php` as the starting point; just copy it and rename it.
56 |
57 | Configuration options are not explained in this project as they come from the [OneLogin project](https://github.com/onelogin/php-saml), please refer there for details.
58 |
59 | The only real difference between this config and the one that OneLogin uses, is that the SP `entityId`, `assertionConsumerService` URL and `singleLogoutService` URL are injected by the library.
60 |
61 | If you don't specify URLs in the corresponding IDP config optional values, this library provides defaults values. The library creates the `metadata`, `acs`, and `sls` routes for each IDP. If you specify different values in the config, note that the `acs` and `sls` URLs should correspond to actual routes that you set up that are directed to the corresponding Saml2Controller function.
62 |
63 | If you want to optionally define values in ENV vars instead of the `{idpName}_idp_settings` file, you'll see in there that there is a naming pattern you can follow for ENV values. For example, if in `mytestipd1_idp_settings.php` you set `$this_idp_env_id = 'mytestidp1';`, and in `myidp2_idp_settings.php` you set `$this_idp_env_id = 'myidp2'`, then you can set ENV vars starting with `SAML2_mytestidp1_` and `SAML2_myidp2_` respectively.
64 |
65 | For example, it can be:
66 |
67 | **.env**
68 | ```env
69 | SAML2_mytestidp1_SP_x509="..."
70 | SAML2_mytestidp1_SP_PRIVATEKEY="..."
71 | // Other SAML2_mytestidp1_* values
72 |
73 | SAML2_myidp2_SP_x509="..."
74 | SAML2_myidp2_SP_PRIVATEKEY="..."
75 | // Other SAML2_myidp2_* values
76 | ```
77 |
78 | #### URLs To Pass to The IDP configuration
79 | As mentioned above, you don't need to implement the SP `entityId`, `assertionConsumerService` URL and `singleLogoutService` URL routes, because Saml2Controller already does by default. But you need to know these routes, to provide them to the configuration of your actual IDP, i.e. the 3rd party you are asking to authenticate users.
80 |
81 | You can check the actual routes in the metadata, by navigating to `http(s)://{laravel_url}/{idpName}/metadata`, e.g. `http(s)://{laravel_url}/mytestidp1/metadata` which incidentally will be the default entityId for this SP.
82 |
83 | If you configure the optional `routesPrefix` setting in `saml2_settings.php`, then all idp routes will be prefixed by that value, so you'll need to adjust the metadata url accordingly. For example, if you configure `routesPrefix` to be `'single_sign_on'`, then your IDP metadata for `mytestidp1` will be found at `http(s)://{laravel_url}/single_sign_on/mytestidp1/metadata`.
84 |
85 | The routes automatically created by the library for each IDP are:
86 | - `{routesPrefix}/{idpName}/logout`
87 | - `{routesPrefix}/{idpName}/login`
88 | - `{routesPrefix}/{idpName}/metadata`
89 | - `{routesPrefix}/{idpName}/acs`
90 | - `{routesPrefix}/{idpName}/sls`
91 |
92 | #### Example: simplesamlphp IDP configuration
93 | If you use simplesamlphp as a test IDP, and your SP metadata url is `http(s)://{laravel_url}/mytestidp1/metadata`, add the following to `/metadata/sp-remote.php` to inform the IDP of your laravel-saml2 SP identity.
94 |
95 | For example, it can be:
96 |
97 | **/metadata/sp-remote.php**
98 | ```php
99 | $metadata['http(s)://{laravel_url}/mytestidp1/metadata'] = array(
100 | 'AssertionConsumerService' => 'http(s)://{laravel_url}/mytestidp1/acs',
101 | 'SingleLogoutService' => 'http(s)://{laravel_url}/mytestidp1/sls',
102 | //the following two affect what the $Saml2user->getUserId() will return
103 | 'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
104 | 'simplesaml.nameidattribute' => 'uid'
105 | );
106 | ```
107 |
108 | ### Usage
109 |
110 | When you want your user to login, just redirect to the login route configured for the particular IDP, `route('saml2_login', 'mytestidp1')`. You can also instantiate a `Saml2Auth` for the desired IDP using the `Saml2Auth::loadOneLoginAuthFromIpdConfig('mytestidp1')` function to load the config and construct the OneLogin auth argment; just remember that it does not use any session storage, so if you ask it to login it will redirect to the IDP whether the user is already logged in or not. For example, you can change your authentication middleware.
111 |
112 | For example, it can be:
113 |
114 | **App/Http/Middleware/RedirectIfAuthenticated.php**
115 | ```php
116 | public function handle($request, Closure $next)
117 | {
118 | if ($this->auth->guest())
119 | {
120 | if ($request->ajax())
121 | {
122 | return response('Unauthorized.', 401); // Or, return a response that causes client side js to redirect to '/routesPrefix/myIdp1/login'
123 | }
124 | else
125 | {
126 | $saml2Auth = new Saml2Auth(Saml2Auth::loadOneLoginAuthFromIpdConfig('mytestidp1'));
127 | return $saml2Auth->login(URL::full());
128 | }
129 | }
130 |
131 | return $next($request);
132 | }
133 | ```
134 |
135 | Since Laravel 5.3, you can change your unauthenticated method.
136 |
137 | For example, it can be:
138 |
139 | **App/Exceptions/Handler.php**
140 | ```php
141 | protected function unauthenticated($request, AuthenticationException $exception)
142 | {
143 | if ($request->expectsJson())
144 | {
145 | return response()->json(['error' => 'Unauthenticated.'], 401); // Or, return a response that causes client side js to redirect to '/routesPrefix/myIdp1/login'
146 | }
147 |
148 | $saml2Auth = new Saml2Auth(Saml2Auth::loadOneLoginAuthFromIpdConfig('mytestidp1'));
149 | return $saml2Auth->login('/my/redirect/path');
150 | }
151 | ```
152 |
153 | For login requests that come through redirects to the login route, `{routesPrefix}/mytestidp1/login`, the default login call does not pass a redirect URL to the Saml login request. That login argument is useful because the ACS handler can gets that value (passed back from the IDP as RelayPath) and by default will redirect there. To pass the redirect URL from the controller login, extend the Saml2Controller class and implement your own `login()` function. Set the `config/saml2_settings.php` value `saml2_controller` to be your extended class so that the routes will direct requests to your controller instead of the default.
154 |
155 | For example, it can be:
156 |
157 | **config/saml_settings.php**
158 | ```
159 | 'saml2_controller' => 'App\Http\Controllers\MyNamespace\MySaml2Controller'
160 | ```
161 | **App/Http/Controllers/MyNamespace/MySaml2Controller.php**
162 | ```php
163 | use Aacotroneo\Saml2\Http\Controllers\Saml2Controller;
164 |
165 | class MySaml2Controller extends Saml2Controller
166 | {
167 | public function login()
168 | {
169 | $loginRedirect = '...'; // Determine redirect URL
170 | $this->saml2Auth->login($loginRedirect);
171 | }
172 | }
173 | ```
174 |
175 | After login is called, the user will be redirected to the IDP login page. Then the IDP, which you have configured with an endpoint the library serves, will call back, e.g. `/mytestidp1/acs` or `/{routesPrefix}/mytestidp1/acs`. That will process the response and fire an event when ready. The next step for you is to handle that event. You just need to login the user or refuse.
176 |
177 | For example, it can be:
178 |
179 | **App/Providers/MyEventServiceProvider.php**
180 | ```php
181 | Event::listen('Aacotroneo\Saml2\Events\Saml2LoginEvent', function (Saml2LoginEvent $event) {
182 | $messageId = $event->getSaml2Auth()->getLastMessageId();
183 | // Add your own code preventing reuse of a $messageId to stop replay attacks
184 |
185 | $user = $event->getSaml2User();
186 | $userData = [
187 | 'id' => $user->getUserId(),
188 | 'attributes' => $user->getAttributes(),
189 | 'assertion' => $user->getRawSamlAssertion()
190 | ];
191 | $laravelUser = //find user by ID or attribute
192 | //if it does not exist create it and go on or show an error message
193 | Auth::login($laravelUser);
194 | });
195 | ```
196 | ### Auth persistence
197 |
198 | Be careful about necessary Laravel middleware for Auth persistence in Session.
199 |
200 | For example, it can be:
201 |
202 | **App/Http/Kernel.php**
203 | ```php
204 | protected $middlewareGroups = [
205 | 'web' => [
206 | ...
207 | ],
208 | 'api' => [
209 | ...
210 | ],
211 | 'saml' => [
212 | \App\Http\Middleware\EncryptCookies::class,
213 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
214 | \Illuminate\Session\Middleware\StartSession::class,
215 | ],
216 | ```
217 | **config/saml2_settings.php**
218 | ```
219 | /**
220 | * which middleware group to use for the saml routes
221 | * Laravel 5.2 will need a group which includes StartSession
222 | */
223 | 'routesMiddleware' => ['saml'],
224 | ```
225 |
226 | ### Log out
227 | Now there are two ways the user can log out.
228 | + 1 - By logging out in your app: In this case you 'should' notify the IDP first so it closes global session.
229 | + 2 - By logging out of the global SSO Session. In this case the IDP will notify you on `/mytestidp1/slo` endpoint (already provided), if the IDP supports SLO
230 |
231 | For case 1, call `Saml2Auth::logout();` or redirect the user to the logout route, e.g. `mytestidp1_logout` which does just that. Do not close the session immediately as you need to receive a response confirmation from the IDP (redirection). That response will be handled by the library at `/mytestidp1/sls` and will fire an event for you to complete the operation.
232 |
233 | For case 2, you will only receive the event. Both cases 1 and 2 receive the same event.
234 |
235 |
236 | Note that for case 2, you may have to manually save your session to make the logout stick (as the session is saved by middleware, but the OneLogin library will redirect back to your IDP before that happens)
237 |
238 | For example, it can be:
239 |
240 | **App/Providers/MyEventServiceProvider.php**
241 | ```php
242 | Event::listen('Aacotroneo\Saml2\Events\Saml2LogoutEvent', function ($event) {
243 | Auth::logout();
244 | Session::save();
245 | });
246 | ```
247 |
248 | That's it. Feel free to ask any questions, make PR or suggestions, or open Issues.
249 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aacotroneo/laravel-saml2",
3 | "description": "A Laravel package for Saml2 integration as a SP (service provider) for multiple IdPs, based on OneLogin toolkit which is much more lightweight than simplesamlphp.",
4 | "keywords": ["laravel","saml", "saml2", "onelogin"],
5 | "homepage": "https://github.com/aacotroneo/laravel-saml2",
6 | "license": "MIT",
7 | "version": "2.1.0",
8 | "authors": [
9 | {
10 | "name": "aacotroneo",
11 | "email": "aacotroneo@gmail.com"
12 | },
13 | {
14 | "name": "Niraj Patkar",
15 | "email": "niraj@alphonso.in"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=5.5.0",
20 | "ext-openssl": "*",
21 | "illuminate/support": ">=5.0.0",
22 | "onelogin/php-saml": "^3.0.0"
23 | },
24 | "require-dev": {
25 | "mockery/mockery": "0.9.*",
26 | "phpunit/phpunit": "~4.0"
27 | },
28 | "autoload": {
29 | "psr-0": {
30 | "Aacotroneo\\Saml2\\": "src/"
31 | }
32 | },
33 | "extra": {
34 | "laravel": {
35 | "providers": [
36 | "Aacotroneo\\Saml2\\Saml2ServiceProvider"
37 | ]
38 | }
39 | },
40 | "minimum-stability": "stable"
41 | }
42 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aacotroneo/laravel-saml2/a515a5a984c137be380ab6a1961c7685f2b9326c/public/.gitkeep
--------------------------------------------------------------------------------
/src/Aacotroneo/Saml2/Events/Saml2Event.php:
--------------------------------------------------------------------------------
1 | idp = $idp;
12 | }
13 |
14 | public function getSaml2Idp()
15 | {
16 | return $this->idp;
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/Aacotroneo/Saml2/Events/Saml2LoginEvent.php:
--------------------------------------------------------------------------------
1 | user = $user;
17 | $this->auth = $auth;
18 | }
19 |
20 | public function getSaml2User()
21 | {
22 | return $this->user;
23 | }
24 |
25 | public function getSaml2Auth()
26 | {
27 | return $this->auth;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Aacotroneo/Saml2/Events/Saml2LogoutEvent.php:
--------------------------------------------------------------------------------
1 | getMetadata();
21 |
22 | return response($metadata, 200, ['Content-Type' => 'text/xml']);
23 | }
24 |
25 | /**
26 | * Process an incoming saml2 assertion request.
27 | * Fires 'Saml2LoginEvent' event if a valid user is found.
28 | *
29 | * @param Saml2Auth $saml2Auth
30 | * @param $idpName
31 | * @return \Illuminate\Http\Response
32 | */
33 | public function acs(Saml2Auth $saml2Auth, $idpName)
34 | {
35 | $errors = $saml2Auth->acs();
36 |
37 | if (!empty($errors)) {
38 | logger()->error('Saml2 error_detail', ['error' => $saml2Auth->getLastErrorReason()]);
39 | session()->flash('saml2_error_detail', [$saml2Auth->getLastErrorReason()]);
40 |
41 | logger()->error('Saml2 error', $errors);
42 | session()->flash('saml2_error', $errors);
43 | return redirect(config('saml2_settings.errorRoute'));
44 | }
45 | $user = $saml2Auth->getSaml2User();
46 |
47 | event(new Saml2LoginEvent($idpName, $user, $saml2Auth));
48 |
49 | $redirectUrl = $user->getIntendedUrl();
50 |
51 | if ($redirectUrl !== null) {
52 | return redirect($redirectUrl);
53 | } else {
54 |
55 | return redirect(config('saml2_settings.loginRoute'));
56 | }
57 | }
58 |
59 | /**
60 | * Process an incoming saml2 logout request.
61 | * Fires 'Saml2LogoutEvent' event if its valid.
62 | * This means the user logged out of the SSO infrastructure, you 'should' log them out locally too.
63 | *
64 | * @param Saml2Auth $saml2Auth
65 | * @param $idpName
66 | * @return \Illuminate\Http\Response
67 | */
68 | public function sls(Saml2Auth $saml2Auth, $idpName)
69 | {
70 | $errors = $saml2Auth->sls($idpName, config('saml2_settings.retrieveParametersFromServer'));
71 | if (!empty($errors)) {
72 | logger()->error('Saml2 error', $errors);
73 | session()->flash('saml2_error', $errors);
74 | throw new \Exception("Could not log out");
75 | }
76 |
77 | return redirect(config('saml2_settings.logoutRoute')); //may be set a configurable default
78 | }
79 |
80 | /**
81 | * Initiate a logout request across all the SSO infrastructure.
82 | *
83 | * @param Saml2Auth $saml2Auth
84 | * @param Request $request
85 | */
86 | public function logout(Saml2Auth $saml2Auth, Request $request)
87 | {
88 | $returnTo = $request->query('returnTo');
89 | $sessionIndex = $request->query('sessionIndex');
90 | $nameId = $request->query('nameId');
91 | $saml2Auth->logout($returnTo, $nameId, $sessionIndex); //will actually end up in the sls endpoint
92 | //does not return
93 | }
94 |
95 | /**
96 | * Initiate a login request.
97 | *
98 | * @param Saml2Auth $saml2Auth
99 | */
100 | public function login(Saml2Auth $saml2Auth)
101 | {
102 | $saml2Auth->login(config('saml2_settings.loginRoute'));
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Aacotroneo/Saml2/Saml2Auth.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
26 | }
27 |
28 | /**
29 | * Load the IDP config file and construct a OneLogin\Saml2\Auth (aliased here as OneLogin_Saml2_Auth).
30 | * Pass the returned value to the Saml2Auth constructor.
31 | *
32 | * @param string $idpName The target IDP name, must correspond to config file 'config/saml2/${idpName}_idp_settings.php'
33 | * @return OneLogin_Saml2_Auth Contructed OneLogin Saml2 configuration of the requested IDP
34 | * @throws \InvalidArgumentException if $idpName is empty
35 | * @throws \Exception if key or certificate is configured to a file path and the file is not found.
36 | */
37 | public static function loadOneLoginAuthFromIpdConfig($idpName)
38 | {
39 | if (empty($idpName)) {
40 | throw new \InvalidArgumentException("IDP name required.");
41 | }
42 |
43 | $config = config('saml2.'.$idpName.'_idp_settings');
44 |
45 | if (is_null($config)) {
46 | throw new \InvalidArgumentException('"' . $idpName . '" is not a valid IdP.');
47 | }
48 |
49 | if (empty($config['sp']['entityId'])) {
50 | $config['sp']['entityId'] = URL::route('saml2_metadata', $idpName);
51 | }
52 | if (empty($config['sp']['assertionConsumerService']['url'])) {
53 | $config['sp']['assertionConsumerService']['url'] = URL::route('saml2_acs', $idpName);
54 | }
55 | if (!empty($config['sp']['singleLogoutService']) &&
56 | empty($config['sp']['singleLogoutService']['url'])) {
57 | $config['sp']['singleLogoutService']['url'] = URL::route('saml2_sls', $idpName);
58 | }
59 | if (strpos($config['sp']['privateKey'], 'file://')===0) {
60 | $config['sp']['privateKey'] = static::extractPkeyFromFile($config['sp']['privateKey']);
61 | }
62 | if (strpos($config['sp']['x509cert'], 'file://')===0) {
63 | $config['sp']['x509cert'] = static::extractCertFromFile($config['sp']['x509cert']);
64 | }
65 | if (strpos($config['idp']['x509cert'], 'file://')===0) {
66 | $config['idp']['x509cert'] = static::extractCertFromFile($config['idp']['x509cert']);
67 | }
68 |
69 | return new OneLogin_Saml2_Auth($config);
70 | }
71 |
72 | /**
73 | * @return bool if a valid user was fetched from the saml assertion this request.
74 | */
75 | function isAuthenticated()
76 | {
77 | $auth = $this->auth;
78 |
79 | return $auth->isAuthenticated();
80 | }
81 |
82 | /**
83 | * The user info from the assertion
84 | * @return Saml2User
85 | */
86 | function getSaml2User()
87 | {
88 | return new Saml2User($this->auth);
89 | }
90 |
91 | /**
92 | * The ID of the last message processed
93 | * @return String
94 | */
95 | function getLastMessageId()
96 | {
97 | return $this->auth->getLastMessageId();
98 | }
99 |
100 | /**
101 | * Initiate a saml2 login flow. It will redirect! Before calling this, check if user is
102 | * authenticated (here in saml2). That would be true when the assertion was received this request.
103 | *
104 | * @param string|null $returnTo The target URL the user should be returned to after login.
105 | * @param array $parameters Extra parameters to be added to the GET
106 | * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true'
107 | * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true'
108 | * @param bool $stay True if we want to stay (returns the url string) False to redirect
109 | * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy element
110 | *
111 | * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
112 | */
113 | function login($returnTo = null, $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true)
114 | {
115 | $auth = $this->auth;
116 |
117 | return $auth->login($returnTo, $parameters, $forceAuthn, $isPassive, $stay, $setNameIdPolicy);
118 | }
119 |
120 | /**
121 | * Initiate a saml2 logout flow. It will close session on all other SSO services. You should close
122 | * local session if applicable.
123 | *
124 | * @param string|null $returnTo The target URL the user should be returned to after logout.
125 | * @param string|null $nameId The NameID that will be set in the LogoutRequest.
126 | * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process).
127 | * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest.
128 | * @param bool $stay True if we want to stay (returns the url string) False to redirect
129 | * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest.
130 | *
131 | * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
132 | *
133 | * @throws OneLogin_Saml2_Error
134 | */
135 | function logout($returnTo = null, $nameId = null, $sessionIndex = null, $nameIdFormat = null, $stay = false, $nameIdNameQualifier = null)
136 | {
137 | $auth = $this->auth;
138 |
139 | return $auth->logout($returnTo, [], $nameId, $sessionIndex, $stay, $nameIdFormat, $nameIdNameQualifier);
140 | }
141 |
142 | /**
143 | * Process a Saml response (assertion consumer service)
144 | * When errors are encountered, it returns an array with proper description
145 | */
146 | function acs()
147 | {
148 |
149 | /** @var $auth OneLogin_Saml2_Auth */
150 | $auth = $this->auth;
151 |
152 | $auth->processResponse();
153 |
154 | $errors = $auth->getErrors();
155 |
156 | if (!empty($errors)) {
157 | return array('error' => $errors, 'last_error_reason' => $auth->getLastErrorReason());
158 | }
159 |
160 | if (!$auth->isAuthenticated()) {
161 | return array('error' => 'Could not authenticate', 'last_error_reason' => $auth->getLastErrorReason());
162 | }
163 |
164 | return null;
165 |
166 | }
167 |
168 | /**
169 | * Process a Saml response (assertion consumer service)
170 | * returns an array with errors if it can not logout
171 | */
172 | function sls($idp, $retrieveParametersFromServer = false)
173 | {
174 | $auth = $this->auth;
175 |
176 | // destroy the local session by firing the Logout event
177 | $keep_local_session = false;
178 | $session_callback = function () use ($idp) {
179 | event(new Saml2LogoutEvent($idp));
180 | };
181 |
182 | $auth->processSLO($keep_local_session, null, $retrieveParametersFromServer, $session_callback);
183 |
184 | $errors = $auth->getErrors();
185 |
186 | if (!empty($errors)) {
187 | return array('error' => $errors, 'last_error_reason' => $auth->getLastErrorReason());
188 | }
189 |
190 | return null;
191 |
192 | }
193 |
194 | /**
195 | * Show metadata about the local sp. Use this to configure your saml2 IDP
196 | * @return mixed xml string representing metadata
197 | * @throws \InvalidArgumentException if metadata is not correctly set
198 | */
199 | function getMetadata()
200 | {
201 | $auth = $this->auth;
202 | $settings = $auth->getSettings();
203 | $metadata = $settings->getSPMetadata();
204 | $errors = $settings->validateMetadata($metadata);
205 |
206 | if (empty($errors)) {
207 |
208 | return $metadata;
209 | } else {
210 |
211 | throw new InvalidArgumentException(
212 | 'Invalid SP metadata: ' . implode(', ', $errors),
213 | OneLogin_Saml2_Error::METADATA_SP_INVALID
214 | );
215 | }
216 | }
217 |
218 | /**
219 | * Get the last error reason from \OneLogin_Saml2_Auth, useful for error debugging.
220 | * @see \OneLogin_Saml2_Auth::getLastErrorReason()
221 | * @return string
222 | */
223 | function getLastErrorReason() {
224 | return $this->auth->getLastErrorReason();
225 | }
226 |
227 |
228 | protected static function extractPkeyFromFile($path) {
229 | $res = openssl_get_privatekey($path);
230 | if (empty($res)) {
231 | throw new \Exception('Could not read private key-file at path \'' . $path . '\'');
232 | }
233 | openssl_pkey_export($res, $pkey);
234 | openssl_pkey_free($res);
235 | return static::extractOpensslString($pkey, 'PRIVATE KEY');
236 | }
237 |
238 | protected static function extractCertFromFile($path) {
239 | $res = openssl_x509_read(file_get_contents($path));
240 | if (empty($res)) {
241 | throw new \Exception('Could not read X509 certificate-file at path \'' . $path . '\'');
242 | }
243 | openssl_x509_export($res, $cert);
244 | openssl_x509_free($res);
245 | return static::extractOpensslString($cert, 'CERTIFICATE');
246 | }
247 |
248 | protected static function extractOpensslString($keyString, $delimiter) {
249 | $keyString = str_replace(["\r", "\n"], "", $keyString);
250 | $regex = '/-{5}BEGIN(?:\s|\w)+' . $delimiter . '-{5}\s*(.+?)\s*-{5}END(?:\s|\w)+' . $delimiter . '-{5}/m';
251 | preg_match($regex, $keyString, $matches);
252 | return empty($matches[1]) ? '' : $matches[1];
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/src/Aacotroneo/Saml2/Saml2ServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishes([
29 | __DIR__.'/../../config/saml2_settings.php' => config_path('saml2_settings.php'),
30 | __DIR__.'/../../config/test_idp_settings.php' => config_path('saml2'.DIRECTORY_SEPARATOR.'test_idp_settings.php'),
31 | ]);
32 |
33 | if (config('saml2_settings.proxyVars', false)) {
34 | OneLogin_Saml2_Utils::setProxyVars(true);
35 | }
36 | }
37 |
38 | /**
39 | * Register the service provider.
40 | *
41 | * @return void
42 | */
43 | public function register()
44 | {
45 | $this->app->singleton(Saml2Auth::class, function ($app) {
46 | $idpName = $app->request->route('idpName');
47 | $auth = Saml2Auth::loadOneLoginAuthFromIpdConfig($idpName);
48 | return new Saml2Auth($auth);
49 | });
50 | }
51 |
52 | /**
53 | * Get the services provided by the provider.
54 | *
55 | * @return array
56 | */
57 | public function provides()
58 | {
59 | return [Saml2Auth::class];
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/Aacotroneo/Saml2/Saml2User.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
20 | }
21 |
22 | /**
23 | * @return string User Id retrieved from assertion processed this request
24 | */
25 | function getUserId()
26 | {
27 | $auth = $this->auth;
28 |
29 | return $auth->getNameId();
30 |
31 | }
32 |
33 | /**
34 | * @return array attributes retrieved from assertion processed this request
35 | */
36 | function getAttributes()
37 | {
38 | $auth = $this->auth;
39 |
40 | return $auth->getAttributes();
41 | }
42 |
43 | /**
44 | * Returns the requested SAML attribute
45 | *
46 | * @param string $name The requested attribute of the user.
47 | * @return array|null Requested SAML attribute ($name).
48 | */
49 | function getAttribute($name) {
50 | $auth = $this->auth;
51 |
52 | return $auth->getAttribute($name);
53 | }
54 |
55 | /**
56 | * @return array attributes retrieved from assertion processed this request
57 | */
58 | function getAttributesWithFriendlyName()
59 | {
60 | $auth = $this->auth;
61 |
62 | return $auth->getAttributesWithFriendlyName();
63 | }
64 |
65 | /**
66 | * @return string the saml assertion processed this request
67 | */
68 | function getRawSamlAssertion()
69 | {
70 | return app('request')->input('SAMLResponse'); //just this request
71 | }
72 |
73 | function getIntendedUrl()
74 | {
75 | $relayState = app('request')->input('RelayState'); //just this request
76 |
77 | $url = app('Illuminate\Contracts\Routing\UrlGenerator');
78 |
79 | if ($relayState && $url->full() != $relayState) {
80 |
81 | return $relayState;
82 | }
83 | }
84 |
85 | /**
86 | * Parses a SAML property and adds this property to this user or returns the value
87 | *
88 | * @param string $samlAttribute
89 | * @param string $propertyName
90 | * @return array|null
91 | */
92 | function parseUserAttribute($samlAttribute = null, $propertyName = null) {
93 | if(empty($samlAttribute)) {
94 | return null;
95 | }
96 | if(empty($propertyName)) {
97 | return $this->getAttribute($samlAttribute);
98 | }
99 |
100 | return $this->{$propertyName} = $this->getAttribute($samlAttribute);
101 | }
102 |
103 | /**
104 | * Parse the saml attributes and adds it to this user
105 | *
106 | * @param array $attributes Array of properties which need to be parsed, like this ['email' => 'urn:oid:0.9.2342.19200300.100.1.3']
107 | */
108 | function parseAttributes($attributes = array()) {
109 | foreach($attributes as $propertyName => $samlAttribute) {
110 | $this->parseUserAttribute($samlAttribute, $propertyName);
111 | }
112 | }
113 |
114 | function getSessionIndex()
115 | {
116 | return $this->auth->getSessionIndex();
117 | }
118 |
119 | function getNameId()
120 | {
121 | return $this->auth->getNameId();
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/config/mytestidp1_idp_settings.php:
--------------------------------------------------------------------------------
1 | true, //@todo: make this depend on laravel config
22 |
23 | // Enable debug mode (to print errors)
24 | 'debug' => env('APP_DEBUG', false),
25 |
26 | // Service Provider Data that we are deploying
27 | 'sp' => array(
28 |
29 | // Specifies constraints on the name identifier to be used to
30 | // represent the requested subject.
31 | // Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
32 | 'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
33 |
34 | // Usually x509cert and privateKey of the SP are provided by files placed at
35 | // the certs folder. But we can also provide them with the following parameters
36 | 'x509cert' => env('SAML2_'.$this_idp_env_id.'_SP_x509',''),
37 | 'privateKey' => env('SAML2_'.$this_idp_env_id.'_SP_PRIVATEKEY',''),
38 |
39 | // Identifier (URI) of the SP entity.
40 | // Leave blank to use the '{idpName}_metadata' route, e.g. 'test_metadata'.
41 | 'entityId' => env('SAML2_'.$this_idp_env_id.'_SP_ENTITYID',''),
42 |
43 | // Specifies info about where and how the message MUST be
44 | // returned to the requester, in this case our SP.
45 | 'assertionConsumerService' => array(
46 | // URL Location where the from the IdP will be returned,
47 | // using HTTP-POST binding.
48 | // Leave blank to use the '{idpName}_acs' route, e.g. 'test_acs'
49 | 'url' => '',
50 | ),
51 | // Specifies info about where and how the message MUST be
52 | // returned to the requester, in this case our SP.
53 | // Remove this part to not include any URL Location in the metadata.
54 | 'singleLogoutService' => array(
55 | // URL Location where the from the IdP will be returned,
56 | // using HTTP-Redirect binding.
57 | // Leave blank to use the '{idpName}_sls' route, e.g. 'test_sls'
58 | 'url' => '',
59 | ),
60 | ),
61 |
62 | // Identity Provider Data that we want connect with our SP
63 | 'idp' => array(
64 | // Identifier of the IdP entity (must be a URI)
65 | 'entityId' => env('SAML2_'.$this_idp_env_id.'_IDP_ENTITYID', $idp_host . '/saml2/idp/metadata.php'),
66 | // SSO endpoint info of the IdP. (Authentication Request protocol)
67 | 'singleSignOnService' => array(
68 | // URL Target of the IdP where the SP will send the Authentication Request Message,
69 | // using HTTP-Redirect binding.
70 | 'url' => env('SAML2_'.$this_idp_env_id.'_IDP_SSO_URL', $idp_host . '/saml2/idp/SSOService.php'),
71 | ),
72 | // SLO endpoint info of the IdP.
73 | 'singleLogoutService' => array(
74 | // URL Location of the IdP where the SP will send the SLO Request,
75 | // using HTTP-Redirect binding.
76 | 'url' => env('SAML2_'.$this_idp_env_id.'_IDP_SL_URL', $idp_host . '/saml2/idp/SingleLogoutService.php'),
77 | ),
78 | // Public x509 certificate of the IdP
79 | 'x509cert' => env('SAML2_'.$this_idp_env_id.'_IDP_x509', 'MIID/TCCAuWgAwIBAgIJAI4R3WyjjmB1MA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJBUjEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMRUwEwYDVQQHDAxCdWVub3MgQWlyZXMxDDAKBgNVBAoMA1NJVTERMA8GA1UECwwIU2lzdGVtYXMxFDASBgNVBAMMC09yZy5TaXUuQ29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbmlAc2l1LmVkdS5hcjAeFw0xNDEyMDExNDM2MjVaFw0yNDExMzAxNDM2MjVaMIGUMQswCQYDVQQGEwJBUjEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMRUwEwYDVQQHDAxCdWVub3MgQWlyZXMxDDAKBgNVBAoMA1NJVTERMA8GA1UECwwIU2lzdGVtYXMxFDASBgNVBAMMC09yZy5TaXUuQ29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbmlAc2l1LmVkdS5hcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbzW/EpEv+qqZzfT1Buwjg9nnNNVrxkCfuR9fQiQw2tSouS5X37W5h7RmchRt54wsm046PDKtbSz1NpZT2GkmHN37yALW2lY7MyVUC7itv9vDAUsFr0EfKIdCKgxCKjrzkZ5ImbNvjxf7eA77PPGJnQ/UwXY7W+cvLkirp0K5uWpDk+nac5W0JXOCFR1BpPUJRbz2jFIEHyChRt7nsJZH6ejzNqK9lABEC76htNy1Ll/D3tUoPaqo8VlKW3N3MZE0DB9O7g65DmZIIlFqkaMH3ALd8adodJtOvqfDU/A6SxuwMfwDYPjoucykGDu1etRZ7dF2gd+W+1Pn7yizPT1q8CAwEAAaNQME4wHQYDVR0OBBYEFPsn8tUHN8XXf23ig5Qro3beP8BuMB8GA1UdIwQYMBaAFPsn8tUHN8XXf23ig5Qro3beP8BuMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGu60odWFiK+DkQekozGnlpNBQz5lQ/bwmOWdktnQj6HYXu43e7sh9oZWArLYHEOyMUekKQAxOK51vbTHzzw66BZU91/nqvaOBfkJyZKGfluHbD0/hfOl/D5kONqI9kyTu4wkLQcYGyuIi75CJs15uA03FSuULQdY/Liv+czS/XYDyvtSLnu43VuAQWN321PQNhuGueIaLJANb2C5qq5ilTBUw6PxY9Z+vtMjAjTJGKEkE/tQs7CvzLPKXX3KTD9lIILmX5yUC3dLgjVKi1KGDqNApYGOMtjr5eoxPQrqDBmyx3flcy0dQTdLXud3UjWVW3N0PYgJtw5yBsS74QTGD4='),
80 | /*
81 | * Instead of use the whole x509cert you can use a fingerprint
82 | * (openssl x509 -noout -fingerprint -in "idp.crt" to generate it)
83 | */
84 | // 'certFingerprint' => '',
85 |
86 | /**
87 | * (Optional) Enable Multi-Cert signing/encryption
88 | * In some scenarios the IdP uses different certificates for
89 | * signing/encryption, or is under key rollover phase and
90 | * more than one certificate is published on IdP metadata.
91 | * In order to handle that the toolkit offers that parameter.
92 | * (when used, 'x509cert' and 'certFingerprint' values are
93 | * ignored).
94 | */
95 |
96 | //'x509certMulti'=>array(
97 | // 'signing'=>array(
98 | // 0=>env('SAML2_'.$this_idp_env_id.'_IDP_x509_SIGNING_0',''),
99 | // ),
100 | // 'encryption'=>array(
101 | // 0=>env('SAML2_'.$this_idp_env_id.'_IDP_x509_ENCRYPTION_0',''),
102 | // ),
103 | // ),
104 |
105 | ),
106 |
107 |
108 |
109 | /***
110 | *
111 | * OneLogin advanced settings
112 | *
113 | *
114 | */
115 | // Security settings
116 | 'security' => array(
117 |
118 | /** signatures and encryptions offered */
119 |
120 | // Indicates that the nameID of the sent by this SP
121 | // will be encrypted.
122 | 'nameIdEncrypted' => false,
123 |
124 | // Indicates whether the messages sent by this SP
125 | // will be signed. [The Metadata of the SP will offer this info]
126 | 'authnRequestsSigned' => false,
127 |
128 | // Indicates whether the messages sent by this SP
129 | // will be signed.
130 | 'logoutRequestSigned' => false,
131 |
132 | // Indicates whether the messages sent by this SP
133 | // will be signed.
134 | 'logoutResponseSigned' => false,
135 |
136 | /* Sign the Metadata
137 | False || True (use sp certs) || array (
138 | keyFileName => 'metadata.key',
139 | certFileName => 'metadata.crt'
140 | )
141 | */
142 | 'signMetadata' => false,
143 |
144 |
145 | /** signatures and encryptions required **/
146 |
147 | // Indicates a requirement for the , and
148 | // elements received by this SP to be signed.
149 | 'wantMessagesSigned' => false,
150 |
151 | // Indicates a requirement for the elements received by
152 | // this SP to be signed. [The Metadata of the SP will offer this info]
153 | 'wantAssertionsSigned' => false,
154 |
155 | // Indicates a requirement for the NameID received by
156 | // this SP to be encrypted.
157 | 'wantNameIdEncrypted' => false,
158 |
159 | // Authentication context.
160 | // Set to false and no AuthContext will be sent in the AuthNRequest,
161 | // Set true or don't present thi parameter and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
162 | // Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'),
163 | 'requestedAuthnContext' => true,
164 | ),
165 |
166 | // Contact information template, it is recommended to suply a technical and support contacts
167 | 'contactPerson' => array(
168 | 'technical' => array(
169 | 'givenName' => 'name',
170 | 'emailAddress' => 'no@reply.com'
171 | ),
172 | 'support' => array(
173 | 'givenName' => 'Support',
174 | 'emailAddress' => 'no@reply.com'
175 | ),
176 | ),
177 |
178 | // Organization information template, the info in en_US lang is recomended, add more if required
179 | 'organization' => array(
180 | 'en-US' => array(
181 | 'name' => 'Name',
182 | 'displayname' => 'Display Name',
183 | 'url' => 'http://url'
184 | ),
185 | ),
186 |
187 | /* Interoperable SAML 2.0 Web Browser SSO Profile [saml2int] http://saml2int.org/profile/current
188 |
189 | 'authnRequestsSigned' => false, // SP SHOULD NOT sign the ,
190 | // MUST NOT assume that the IdP validates the sign
191 | 'wantAssertionsSigned' => true,
192 | 'wantAssertionsEncrypted' => true, // MUST be enabled if SSL/HTTPs is disabled
193 | 'wantNameIdEncrypted' => false,
194 | */
195 |
196 | );
197 |
--------------------------------------------------------------------------------
/src/config/saml2_settings.php:
--------------------------------------------------------------------------------
1 | ['test1', 'test2', 'test3'],
7 | * Separate routes will be automatically registered for each IDP specified with IDP name as prefix
8 | * Separate config file saml2/_idp_settings.php should be added & configured accordingly
9 | */
10 | 'idpNames' => ['test'],
11 |
12 | /**
13 | * If 'useRoutes' is set to true, the package defines five new routes for reach entry in idpNames:
14 | *
15 | * Method | URI | Name
16 | * -------|------------------------------------|------------------
17 | * POST | {routesPrefix}/{idpName}/acs | saml_acs
18 | * GET | {routesPrefix}/{idpName}/login | saml_login
19 | * GET | {routesPrefix}/{idpName}/logout | saml_logout
20 | * GET | {routesPrefix}/{idpName}/metadata | saml_metadata
21 | * GET | {routesPrefix}/{idpName}/sls | saml_sls
22 | */
23 | 'useRoutes' => true,
24 |
25 | /**
26 | * Optional, leave empty if you want the defined routes to be top level, i.e. "/{idpName}/*"
27 | */
28 | 'routesPrefix' => '/saml2',
29 |
30 | /**
31 | * which middleware group to use for the saml routes
32 | * Laravel 5.2 will need a group which includes StartSession
33 | */
34 | 'routesMiddleware' => [],
35 |
36 | /**
37 | * Indicates how the parameters will be
38 | * retrieved from the sls request for signature validation
39 | */
40 | 'retrieveParametersFromServer' => false,
41 |
42 | /**
43 | * Where to redirect after logout
44 | */
45 | 'logoutRoute' => '/',
46 |
47 | /**
48 | * Where to redirect after login if no other option was provided
49 | */
50 | 'loginRoute' => '/',
51 |
52 | /**
53 | * Where to redirect after login if no other option was provided
54 | */
55 | 'errorRoute' => '/',
56 |
57 | // If 'proxyVars' is True, then the Saml lib will trust proxy headers
58 | // e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if
59 | // your application is running behind a load balancer which terminates
60 | // SSL.
61 | 'proxyVars' => false,
62 |
63 | /**
64 | * (Optional) Which class implements the route functions.
65 | * If commented out, defaults to this lib's controller (Aacotroneo\Saml2\Http\Controllers\Saml2Controller).
66 | * If you need to extend Saml2Controller (e.g. to override the `login()` function to pass
67 | * a `$returnTo` argument), this value allows you to pass your own controller, and have
68 | * it used in the routes definition.
69 | */
70 | // 'saml2_controller' => '',
71 | );
72 |
--------------------------------------------------------------------------------
/src/routes.php:
--------------------------------------------------------------------------------
1 | prefix(config('saml2_settings.routesPrefix').'/')->group(function() {
5 | Route::prefix('{idpName}')->group(function() {
6 | $saml2_controller = config('saml2_settings.saml2_controller', 'Aacotroneo\Saml2\Http\Controllers\Saml2Controller');
7 |
8 | Route::get('/logout', array(
9 | 'as' => 'saml2_logout',
10 | 'uses' => $saml2_controller.'@logout',
11 | ));
12 |
13 | Route::get('/login', array(
14 | 'as' => 'saml2_login',
15 | 'uses' => $saml2_controller.'@login',
16 | ));
17 |
18 | Route::get('/metadata', array(
19 | 'as' => 'saml2_metadata',
20 | 'uses' => $saml2_controller.'@metadata',
21 | ));
22 |
23 | Route::post('/acs', array(
24 | 'as' => 'saml2_acs',
25 | 'uses' => $saml2_controller.'@acs',
26 | ));
27 |
28 | Route::get('/sls', array(
29 | 'as' => 'saml2_sls',
30 | 'uses' => $saml2_controller.'@sls',
31 | ));
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/tests/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aacotroneo/laravel-saml2/a515a5a984c137be380ab6a1961c7685f2b9326c/tests/.gitkeep
--------------------------------------------------------------------------------
/tests/Saml2/Saml2AuthServiceProviderTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
22 | /**
23 | * Cant test here. It uses Laravel dependencies (eg. config())
24 | */
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Saml2/Saml2AuthTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('isAuthenticated')->andReturn('return');
26 |
27 | $this->assertEquals('return', $saml2->isAuthenticated());
28 |
29 | }
30 |
31 | public function testLogin()
32 | {
33 | $auth = m::mock('OneLogin\Saml2\Auth');
34 | $saml2 = new Saml2Auth($auth);
35 | $auth->shouldReceive('login')->once();
36 | $saml2->login();
37 | }
38 |
39 | public function testLogout()
40 | {
41 | $expectedReturnTo = 'http://localhost';
42 | $expectedSessionIndex = 'session_index_value';
43 | $expectedNameId = 'name_id_value';
44 | $expectedNameIdFormat = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified';
45 | $expectedStay = true;
46 | $expectedNameIdNameQualifier = 'name_id_name_qualifier';
47 | $auth = m::mock('OneLogin\Saml2\Auth');
48 | $saml2 = new Saml2Auth($auth);
49 | $auth->shouldReceive('logout')
50 | ->with($expectedReturnTo, [], $expectedNameId, $expectedSessionIndex, $expectedStay, $expectedNameIdFormat, $expectedNameIdNameQualifier)
51 | ->once();
52 | $saml2->logout($expectedReturnTo, $expectedNameId, $expectedSessionIndex, $expectedNameIdFormat, $expectedStay, $expectedNameIdNameQualifier);
53 | }
54 |
55 |
56 | public function testAcsError()
57 | {
58 | $auth = m::mock('OneLogin\Saml2\Auth');
59 | $saml2 = new Saml2Auth($auth);
60 | $auth->shouldReceive('processResponse')->once();
61 | $auth->shouldReceive('getErrors')->once()->andReturn(array('errors'));
62 | $auth->shouldReceive('getLastErrorReason')->once()->andReturn('last_error_reason');
63 |
64 | $error = $saml2->acs();
65 |
66 | $this->assertNotEmpty($error);
67 | }
68 |
69 |
70 | public function testAcsNotAutenticated()
71 | {
72 | $auth = m::mock('OneLogin\Saml2\Auth');
73 | $saml2 = new Saml2Auth($auth);
74 | $auth->shouldReceive('processResponse')->once();
75 | $auth->shouldReceive('getErrors')->once()->andReturn(null);
76 | $auth->shouldReceive('getLastErrorReason')->once()->andReturn(null);
77 | $auth->shouldReceive('isAuthenticated')->once()->andReturn(false);
78 | $error = $saml2->acs();
79 |
80 | $this->assertNotEmpty($error);
81 | }
82 |
83 |
84 | public function testAcsOK()
85 | {
86 | $auth = m::mock('OneLogin\Saml2\Auth');
87 | $saml2 = new Saml2Auth($auth);
88 | $auth->shouldReceive('processResponse')->once();
89 | $auth->shouldReceive('getErrors')->once()->andReturn(null);
90 | $auth->shouldReceive('isAuthenticated')->once()->andReturn(true);
91 |
92 | $error = $saml2->acs();
93 |
94 | $this->assertEmpty($error);
95 | }
96 |
97 | public function testSlsError()
98 | {
99 | $auth = m::mock('OneLogin\Saml2\Auth');
100 | $saml2 = new Saml2Auth($auth);
101 | $auth->shouldReceive('processSLO')->once();
102 | $auth->shouldReceive('getErrors')->once()->andReturn('errors');
103 | $auth->shouldReceive('getLastErrorReason')->once()->andReturn('last_error_reason');
104 |
105 | $error = $saml2->sls('test');
106 |
107 | $this->assertNotEmpty($error);
108 | }
109 |
110 | public function testSlsOK()
111 | {
112 | $auth = m::mock('OneLogin\Saml2\Auth');
113 | $saml2 = new Saml2Auth($auth);
114 | $auth->shouldReceive('processSLO')->once();
115 | $auth->shouldReceive('getErrors')->once()->andReturn(null);
116 |
117 | $error = $saml2->sls('test');
118 |
119 | $this->assertEmpty($error);
120 | }
121 |
122 | public function testCanGetLastError()
123 | {
124 | $auth = m::mock('OneLogin\Saml2\Auth');
125 | $saml2 = new Saml2Auth($auth);
126 |
127 | $auth->shouldReceive('getLastErrorReason')->andReturn('lastError');
128 |
129 | $this->assertSame('lastError', $saml2->getLastErrorReason());
130 | }
131 |
132 | public function testGetUserAttribute() {
133 | $auth = m::mock('OneLogin\Saml2\Auth');
134 | $saml2 = new Saml2Auth($auth);
135 |
136 | $user = $saml2->getSaml2User();
137 |
138 | $auth->shouldReceive('getAttribute')
139 | ->with('urn:oid:0.9.2342.19200300.100.1.3')
140 | ->andReturn(['test@example.com']);
141 |
142 | $this->assertEquals(['test@example.com'], $user->getAttribute('urn:oid:0.9.2342.19200300.100.1.3'));
143 | }
144 |
145 | public function testParseSingleUserAttribute() {
146 | $auth = m::mock('OneLogin\Saml2\Auth');
147 | $saml2 = new Saml2Auth($auth);
148 |
149 | $user = $saml2->getSaml2User();
150 |
151 | $auth->shouldReceive('getAttribute')
152 | ->with('urn:oid:0.9.2342.19200300.100.1.3')
153 | ->andReturn(['test@example.com']);
154 |
155 | $user->parseUserAttribute('urn:oid:0.9.2342.19200300.100.1.3', 'email');
156 |
157 | $this->assertEquals($user->email, ['test@example.com']);
158 | }
159 |
160 | public function testParseMultipleUserAttributes() {
161 | $auth = m::mock('OneLogin\Saml2\Auth');
162 | $saml2 = new Saml2Auth($auth);
163 |
164 | $user = $saml2->getSaml2User();
165 |
166 | $auth->shouldReceive('getAttribute')
167 | ->twice()
168 | ->andReturn(['test@example.com'], ['Test User']);
169 |
170 | $user->parseAttributes([
171 | 'email' => 'urn:oid:0.9.2342.19200300.100.1.3',
172 | 'displayName' => 'urn:oid:2.16.840.1.113730.3.1.241'
173 | ]);
174 |
175 | $this->assertEquals($user->email, ['test@example.com']);
176 | $this->assertEquals($user->displayName, ['Test User']);
177 | }
178 |
179 | /**
180 | * Cant test here. It uses Laravel dependencies (eg. config())
181 | */
182 |
183 | // $app = m::mock('Illuminate\Contracts\Foundation\Application[register,setDeferredServices]');
184 | //
185 | // $s = m::mock('Aacotroneo\Saml2\Saml2ServiceProvider[publishes]', array($app));
186 | // $s->boot();
187 | // $s->shouldReceive('publishes');
188 | //
189 |
190 | // $repo = m::mock('Illuminate\Foundation\ProviderRepository[createProvider,loadManifest,shouldRecompile]', array($app, m::mock('Illuminate\Filesystem\Filesystem'), array(__DIR__.'/services.json')));
191 | // $repo->shouldReceive('loadManifest')->once()->andReturn(array('eager' => array('foo'), 'deferred' => array('deferred'), 'providers' => array('providers'), 'when' => array()));
192 | // $repo->shouldReceive('shouldRecompile')->once()->andReturn(false);
193 | // $provider = m::mock('Illuminate\Support\ServiceProvider');
194 | // $repo->shouldReceive('createProvider')->once()->with('foo')->andReturn($provider);
195 | // $app->shouldReceive('register')->once()->with($provider);
196 | // $app->shouldReceive('runningInConsole')->andReturn(false);
197 | // $app->shouldReceive('setDeferredServices')->once()->with(array('deferred'));
198 | // $repo->load(array());
199 | // $s = new Saml2ServiceProvider();
200 | //
201 | // $mock = \Mockery::mock(array('pi' => 3.1, 'e' => 2.71));
202 | // $this->assertEquals(3.1416, $mock->pi());
203 | // $this->assertEquals(2.71, $mock->e());
204 |
205 | }
206 |
207 |
--------------------------------------------------------------------------------