AuthAction.php 11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\authclient;

use yii\base\Action;
use yii\base\Exception;
12
use yii\base\InvalidConfigException;
13
use yii\base\NotSupportedException;
14
use yii\web\Response;
15 16 17 18 19
use yii\web\HttpException;
use yii\web\NotFoundHttpException;
use Yii;

/**
20
 * AuthAction performs authentication via different auth clients.
21
 * It supports [[OpenId]], [[OAuth1]] and [[OAuth2]] client types.
22 23
 *
 * Usage:
24
 *
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
 * ~~~
 * class SiteController extends Controller
 * {
 *     public function actions()
 *     {
 *         return [
 *             'auth' => [
 *                 'class' => 'yii\authclient\AuthAction',
 *                 'successCallback' => [$this, 'successCallback'],
 *             ],
 *         ]
 *     }
 *
 *     public function successCallback($client)
 *     {
munawer-t committed
40
 *         $attributes = $client->getUserAttributes();
41 42 43 44 45 46 47 48 49
 *         // user login or signup comes here
 *     }
 * }
 * ~~~
 *
 * Usually authentication via external services is performed inside the popup window.
 * This action handles the redirection and closing of popup window correctly.
 *
 * @see Collection
50
 * @see \yii\authclient\widgets\AuthChoice
51
 *
Qiang Xue committed
52 53 54
 * @property string $cancelUrl Cancel URL.
 * @property string $successUrl Successful URL.
 *
55 56 57 58 59
 * @author Paul Klimov <klimov.paul@gmail.com>
 * @since 2.0
 */
class AuthAction extends Action
{
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
    /**
     * @var string name of the auth client collection application component.
     * It should point to [[Collection]] instance.
     */
    public $clientCollection = 'authClientCollection';
    /**
     * @var string name of the GET param, which is used to passed auth client id to this action.
     * Note: watch for the naming, make sure you do not choose name used in some auth protocol.
     */
    public $clientIdGetParamName = 'authclient';
    /**
     * @var callable PHP callback, which should be triggered in case of successful authentication.
     * This callback should accept [[ClientInterface]] instance as an argument.
     * For example:
     *
     * ~~~
     * public function onAuthSuccess($client)
     * {
     *     $attributes = $client->getUserAttributes();
     *     // user login or signup comes here
     * }
     * ~~~
     *
     * If this callback returns [[Response]] instance, it will be used as action response,
     * otherwise redirection to [[successUrl]] will be performed.
     *
     */
    public $successCallback;
    /**
     * @var string the redirect url after successful authorization.
     */
    private $_successUrl = '';
    /**
     * @var string the redirect url after unsuccessful authorization (e.g. user canceled).
     */
    private $_cancelUrl = '';
96 97


98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
    /**
     * @var string name or alias of the view file, which should be rendered in order to perform redirection.
     * If not set default one will be used.
     */
    public $redirectView;

    /**
     * @param string $url successful URL.
     */
    public function setSuccessUrl($url)
    {
        $this->_successUrl = $url;
    }

    /**
     * @return string successful URL.
     */
    public function getSuccessUrl()
    {
        if (empty($this->_successUrl)) {
            $this->_successUrl = $this->defaultSuccessUrl();
        }

        return $this->_successUrl;
    }

    /**
     * @param string $url cancel URL.
     */
    public function setCancelUrl($url)
    {
        $this->_cancelUrl = $url;
    }

    /**
     * @return string cancel URL.
     */
    public function getCancelUrl()
    {
        if (empty($this->_cancelUrl)) {
            $this->_cancelUrl = $this->defaultCancelUrl();
        }

        return $this->_cancelUrl;
    }

    /**
145
     * Creates default [[successUrl]] value.
146 147 148 149 150 151 152 153
     * @return string success URL value.
     */
    protected function defaultSuccessUrl()
    {
        return Yii::$app->getUser()->getReturnUrl();
    }

    /**
154
     * Creates default [[cancelUrl]] value.
155 156 157 158 159 160 161 162 163 164 165 166 167 168
     * @return string cancel URL value.
     */
    protected function defaultCancelUrl()
    {
        return Yii::$app->getRequest()->getAbsoluteUrl();
    }

    /**
     * Runs the action.
     */
    public function run()
    {
        if (!empty($_GET[$this->clientIdGetParamName])) {
            $clientId = $_GET[$this->clientIdGetParamName];
169
            /* @var $collection \yii\authclient\Collection */
170
            $collection = Yii::$app->get($this->clientCollection);
171 172 173 174 175 176 177 178 179 180 181 182
            if (!$collection->hasClient($clientId)) {
                throw new NotFoundHttpException("Unknown auth client '{$clientId}'");
            }
            $client = $collection->getClient($clientId);

            return $this->auth($client);
        } else {
            throw new NotFoundHttpException();
        }
    }

    /**
183 184
     * @param mixed $client auth client instance.
     * @return Response response instance.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
     * @throws \yii\base\NotSupportedException on invalid client.
     */
    protected function auth($client)
    {
        if ($client instanceof OpenId) {
            return $this->authOpenId($client);
        } elseif ($client instanceof OAuth2) {
            return $this->authOAuth2($client);
        } elseif ($client instanceof OAuth1) {
            return $this->authOAuth1($client);
        } else {
            throw new NotSupportedException('Provider "' . get_class($client) . '" is not supported.');
        }
    }

    /**
     * This method is invoked in case of successful authentication via auth client.
202
     * @param ClientInterface $client auth client instance.
203
     * @throws InvalidConfigException on invalid success callback.
204
     * @return Response response instance.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
     */
    protected function authSuccess($client)
    {
        if (!is_callable($this->successCallback)) {
            throw new InvalidConfigException('"' . get_class($this) . '::successCallback" should be a valid callback.');
        }
        $response = call_user_func($this->successCallback, $client);
        if ($response instanceof Response) {
            return $response;
        }
        return $this->redirectSuccess();
    }

    /**
     * Redirect to the given URL or simply close the popup window.
220 221
     * @param mixed $url URL to redirect, could be a string or array config to generate a valid URL.
     * @param boolean $enforceRedirect indicates if redirect should be performed even in case of popup window.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
     * @return \yii\web\Response response instance.
     */
    public function redirect($url, $enforceRedirect = true)
    {
        $viewFile = $this->redirectView;
        if ($viewFile === null) {
            $viewFile = __DIR__ . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'redirect.php';
        } else {
            $viewFile = Yii::getAlias($viewFile);
        }
        $viewData = [
            'url' => $url,
            'enforceRedirect' => $enforceRedirect,
        ];
        $response = Yii::$app->getResponse();
        $response->content = Yii::$app->getView()->renderFile($viewFile, $viewData);
        return $response;
    }

    /**
242
     * Redirect to the URL. If URL is null, [[successUrl]] will be used.
243
     * @param string $url URL to redirect.
244 245 246 247 248 249 250 251 252 253 254
     * @return \yii\web\Response response instance.
     */
    public function redirectSuccess($url = null)
    {
        if ($url === null) {
            $url = $this->getSuccessUrl();
        }
        return $this->redirect($url);
    }

    /**
255
     * Redirect to the [[cancelUrl]] or simply close the popup window.
256
     * @param string $url URL to redirect.
257 258 259 260 261 262 263 264 265 266 267 268
     * @return \yii\web\Response response instance.
     */
    public function redirectCancel($url = null)
    {
        if ($url === null) {
            $url = $this->getCancelUrl();
        }
        return $this->redirect($url, false);
    }

    /**
     * Performs OpenID auth flow.
269 270 271
     * @param OpenId $client auth client instance.
     * @return Response action response.
     * @throws Exception on failure.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
     * @throws HttpException on failure.
     */
    protected function authOpenId($client)
    {
        if (!empty($_REQUEST['openid_mode'])) {
            switch ($_REQUEST['openid_mode']) {
                case 'id_res':
                    if ($client->validate()) {
                        return $this->authSuccess($client);
                    } else {
                        throw new HttpException(400, 'Unable to complete the authentication because the required data was not received.');
                    }
                    break;
                case 'cancel':
                    $this->redirectCancel();
                    break;
                default:
                    throw new HttpException(400);
                    break;
            }
        } else {
            $url = $client->buildAuthUrl();
            return Yii::$app->getResponse()->redirect($url);
        }

        return $this->redirectCancel();
    }

    /**
     * Performs OAuth1 auth flow.
302
     * @param OAuth1 $client auth client instance.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
     * @return Response action response.
     */
    protected function authOAuth1($client)
    {
        // user denied error
        if (isset($_GET['denied'])) {
            return $this->redirectCancel();
        }

        if (isset($_REQUEST['oauth_token'])) {
            $oauthToken = $_REQUEST['oauth_token'];
        }

        if (!isset($oauthToken)) {
            // Get request token.
            $requestToken = $client->fetchRequestToken();
            // Get authorization URL.
            $url = $client->buildAuthUrl($requestToken);
            // Redirect to authorization URL.
            return Yii::$app->getResponse()->redirect($url);
        } else {
            // Upgrade to access token.
            $client->fetchAccessToken();
            return $this->authSuccess($client);
        }
    }

    /**
     * Performs OAuth2 auth flow.
332 333
     * @param OAuth2 $client auth client instance.
     * @return Response action response.
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
     * @throws \yii\base\Exception on failure.
     */
    protected function authOAuth2($client)
    {
        if (isset($_GET['error'])) {
            if ($_GET['error'] == 'access_denied') {
                // user denied error
                return $this->redirectCancel();
            } else {
                // request error
                if (isset($_GET['error_description'])) {
                    $errorMessage = $_GET['error_description'];
                } elseif (isset($_GET['error_message'])) {
                    $errorMessage = $_GET['error_message'];
                } else {
                    $errorMessage = http_build_query($_GET);
                }
                throw new Exception('Auth error: ' . $errorMessage);
            }
        }

        // Get the access_token and save them to the session.
        if (isset($_GET['code'])) {
            $code = $_GET['code'];
            $token = $client->fetchAccessToken($code);
            if (!empty($token)) {
                return $this->authSuccess($client);
            } else {
                return $this->redirectCancel();
            }
        } else {
            $url = $client->buildAuthUrl();
            return Yii::$app->getResponse()->redirect($url);
        }
    }
AlexGx committed
369
}