Пример #1
0
 /**
  * Listener for {@link User::EVENT_ON_LOGIN_ATTEMPT}
  *
  * Checks if number of attempts wasn't exceeded.
  *
  * @param vBuilder\Security\User user service
  * @param string authentication method - see {@link User::AUTHN_METHOD_INVALID}
  * @param string user id (depending on authentication method)
  *
  * @return void
  */
 public function onLoginAttempt(vBuilder\Security\User $userService, $authMethod, $uid)
 {
     // IPs
     if (!$this->logger->getRemainingAttempts(self::EVENT_IP_LOGIN_ATTEMPT, $this->httpRequest->getRemoteAddress())) {
         throw new AuthenticationException("Maximum number of attempts exceeded for host " . $this->httpRequest->getRemoteAddress(), BaseAuthenticator::MAXIMUM_ATTEMPTS_EXCEEDED);
     }
     // Users
     if ($authMethod == User::AUTHN_METHOD_PASSWORD) {
         if (!$this->logger->getRemainingAttempts(self::EVENT_USER_LOGIN_ATTEMPT, $uid)) {
             throw new AuthenticationException("Maximum number of attempts exceeded for user '{$uid}'", BaseAuthenticator::MAXIMUM_ATTEMPTS_EXCEEDED);
         }
     }
 }
Пример #2
0
 /**
  * @NoAuthorization
  * @Url(/oauth/token)
  */
 function postToken()
 {
     if (!$this->httpRequest->isSecured() && $this->presenter->isInProductionMode()) {
         $this->presenter->terminateWithError(self::ERROR_INVALID_REQUEST, "Secured connection required", 400);
     }
     if (!isset($this->postData['grant_type'])) {
         $this->presenter->terminateWithError(self::ERROR_INVALID_REQUEST, 'Parameter grant_type is required.', 400);
     }
     switch ($this->postData['grant_type']) {
         // RFC 6749 - 4.3. Resource Owner Password Credentials
         // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
         //
         // Used for user owned resources (both user and client application authorization)
         case 'password':
             list($clientId, $clientSecret) = $this->parseClientAuthInfo();
             if (!isset($this->postData['username'])) {
                 $this->presenter->terminateWithError(self::ERROR_INVALID_REQUEST, 'Parameter username is required.', 400);
             }
             if (!isset($this->postData['password'])) {
                 $this->presenter->terminateWithError(self::ERROR_INVALID_REQUEST, 'Parameter password is required.', 400);
             }
             if (!$this->attemptLogger->getRemainingAttempts(RestPresenter::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress())) {
                 $this->presenter->terminateWithError(self::ERROR_MAXIMUM_ATTEMPTS_EXCEEDED, 'Maximum number of authorization attempts exceeded.', 403);
             }
             $this->processClientAuth($clientId, $clientSecret);
             $this->processPasswordAuth($this->postData['username'], $this->postData['password']);
             $token = $this->tokenManager->createToken($this->tokenParameters);
             break;
             // RFC 6749 - 4.4. Client Credentials
             // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
             //
             // Used for resources owned solely by client application owned (no user authorization)
             // Example: B2B services
         // RFC 6749 - 4.4. Client Credentials
         // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
         //
         // Used for resources owned solely by client application owned (no user authorization)
         // Example: B2B services
         case 'client_credentials':
             list($clientId, $clientSecret) = $this->parseClientAuthInfo();
             if (!$this->attemptLogger->getRemainingAttempts(RestPresenter::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress())) {
                 $this->presenter->terminateWithError(self::ERROR_MAXIMUM_ATTEMPTS_EXCEEDED, 'Maximum number of authorization attempts exceeded.', 403);
             }
             $this->processClientAuth($clientId, $clientSecret);
             $token = $this->tokenManager->createToken($this->tokenParameters);
             break;
             // RFC 6749 - 6. Refreshing and Access Token
             // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-6
             //
             // Used for extending time of life of expired token
         // RFC 6749 - 6. Refreshing and Access Token
         // @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-6
         //
         // Used for extending time of life of expired token
         case 'refresh_token':
             list($clientId, $clientSecret) = $this->parseClientAuthInfo();
             if (!isset($this->postData['refresh_token'])) {
                 $this->presenter->terminateWithError(self::ERROR_INVALID_REQUEST, 'Parameter refresh_token is required.', 400);
             }
             if (!$this->attemptLogger->getRemainingAttempts(RestPresenter::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress())) {
                 $this->presenter->terminateWithError(self::ERROR_MAXIMUM_ATTEMPTS_EXCEEDED, 'Maximum number of authorization attempts exceeded.', 403);
             }
             $this->processClientAuth($clientId, $clientSecret);
             $token = $this->tokenManager->refreshToken($this->postData['refresh_token']);
             if (!$token) {
                 $this->attemptLogger->logFail(RestPresenter::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress());
                 $this->presenter->terminateWithError(self::ERROR_INVALID_GRANT, 'Given refresh token is not valid.', 401);
             }
             $this->attemptLogger->logSuccess(RestPresenter::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress());
             break;
             // Unsupported grant_type
         // Unsupported grant_type
         default:
             $this->presenter->terminateWithError(self::ERROR_INVALID_CLIENT, 'Unsupported grant_type requested.', 400);
     }
     // ----------------------------
     // Sanity check
     if (!$token) {
         throw new Nette\InvalidStateException("Expected token");
     }
     $this->httpResponse->setHeader('Cache-Control', 'no-store');
     $payload = new \StdClass();
     $payload->access_token = $token->getToken();
     $payload->token_type = 'Bearer';
     $payload->expires_in = $token->getTtl();
     if ($token->getRefreshToken()) {
         $payload->refresh_token = $token->getRefreshToken();
     }
     return $payload;
 }
Пример #3
0
 /**
  * @return Nette\Application\IResponse
  */
 protected function process(Nette\Application\Request $request)
 {
     // Query output content type -------------------------------------------
     // Accept header is comma separated fallback sequence
     // @todo sequence should be actually sorted by the degree of specificity
     // @todo make support for version options (ie. application/json;version=2)
     // 		see: RESTful Web Services Cookbook page 250
     $cTypes = preg_split('/,/', $this->httpRequest->getHeader('Accept'), 0, PREG_SPLIT_NO_EMPTY);
     foreach ($cTypes as $cType) {
         // We ignore all the options
         $cType = preg_replace('/;.*/', '', $cType);
         if (strcasecmp($cType, 'text/html') === 0 || strcmp($cType, '*/*') === 0) {
             $this->outputContentType = 'text/html';
             $this->httpResponse->setContentType('text/html', 'utf-8');
             break;
         } elseif (strcasecmp($cType, 'application/json') === 0) {
             $this->outputContentType = 'application/json';
             $this->httpResponse->setContentType('application/json', 'utf-8');
             break;
         }
     }
     if ($this->outputContentType === NULL) {
         $this->terminateWithError(self::ERROR_INVALID_REQUEST, "Accept header is missing or not satisfiable.", 406);
     }
     // Process Content-Language header -------------------------------------
     // Process Authorization header ----------------------------------------
     if (($authHeader = $this->httpRequest->getHeader('Authorization')) !== NULL) {
         if (preg_match('/^Bearer\\s([^\\s,;]+)/i', $authHeader, $matches)) {
             $tokenHash = $matches[1];
             // If connection is not secured return error and invalidate sent token
             // just in case
             if (!$request->hasFlag(Nette\Application\Request::SECURED) && $this->isInProductionMode()) {
                 $this->tokenManager->invalidateToken($tokenHash);
                 $this->terminateWithError(self::ERROR_INVALID_REQUEST, "Secured connection required", 400);
             }
             if (!$this->attemptLogger->getRemainingAttempts(self::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress())) {
                 $this->terminateWithError(OAuth2ResourceProvider::ERROR_MAXIMUM_ATTEMPTS_EXCEEDED, 'Maximum number of authorization attempts exceeded.', 403);
             }
             $token = $this->tokenManager->getToken($tokenHash);
             if (!$token) {
                 $this->attemptLogger->logFail(self::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress());
                 $this->httpResponse->addHeader('WWW-Authenticate', 'Bearer realm="' . $this->link() . '"');
                 $this->terminateWithError(OAuth2ResourceProvider::ERROR_INVALID_GRANT, 'Given authorization token is not valid.', 401);
             }
             $this->attemptLogger->logSuccess(self::ATTEMPT_IP_TOKEN, $this->httpRequest->getRemoteAddress());
             if (isset($token->parameters->userIdentity)) {
                 $this->user->login(User::AUTHN_METHOD_INVALID, User::AUTHN_SOURCE_ALL, $token->parameters->userIdentity);
             }
             if (isset($token->parameters->client)) {
                 $this->client = $token->parameters->client;
             }
         }
     }
     // Find request handler ------------------------------------------------
     // Gather resource path
     $parameters = $request->getParameters();
     $resourcePath = isset($parameters[self::PARAM_KEY_PATH]) ? trim($parameters[self::PARAM_KEY_PATH]) : NULL;
     if (!$resourcePath) {
         $this->terminateWithError(self::ERROR_INVALID_REQUEST, "No resource path given.", 400);
     }
     // Request router expects leading slash
     if ($resourcePath[0] != '/') {
         $resourcePath = "/{$resourcePath}";
     }
     // Request router: find resource handler
     try {
         /** @var vBuilder\RestApi\Request */
         $this->resourceRequest = $handlerRequest = $this->requestRouter->createRequest($this->httpRequest->getMethod(), $resourcePath);
     } catch (RequestException $e) {
         $this->terminateWithError(self::ERROR_INVALID_REQUEST, $e->getMessage(), $e->getCode() == RequestException::METHOD_NOT_ALLOWED ? 405 : 404);
     }
     // Request authorization -----------------------------------------------
     $handlerMethodAnnotations = $handlerRequest->getMethodReflection()->getAnnotations();
     if (!isset($handlerMethodAnnotations['NoAuthorization']) || !$handlerMethodAnnotations['NoAuthorization'][0]) {
         if (!$this->client) {
             $this->httpResponse->addHeader('WWW-Authenticate', 'Bearer realm="' . $this->link() . '"');
             $this->terminateWithError(self::ERROR_UNAUTHORIZED, 'Requested resource requires authorization. Please add Authorization header with correct security token.', 401);
         }
     }
     // Decode POST data ----------------------------------------------------
     if ($this->httpRequest->isPost()) {
         $cType = $this->httpRequest->getHeader('Content-Type');
         if (strcasecmp($cType, 'application/json') === 0) {
             try {
                 $this->postData = Nette\Utils\Json::decode(file_get_contents('php://input'), Nette\Utils\Json::FORCE_ARRAY);
             } catch (Nette\Utils\JsonException $e) {
                 $this->terminateWithError(self::ERROR_INVALID_REQUEST, "Malformed POST data (JSON expected).", 400);
             }
         } elseif (strcasecmp($cType, 'application/x-www-form-urlencoded') === 0) {
             $this->postData = $this->httpRequest->getPost();
         } elseif ($cType === NULL) {
             $this->terminateWithError(self::ERROR_INVALID_REQUEST, "Missing Content-Type header, which is mandatory for POST requests.", 400);
         } else {
             $this->terminateWithError(self::ERROR_INVALID_REQUEST, "Request content type of POST data is not supported.", 415);
         }
     }
     // Create resource instance and prepare all dependencies ---------------
     $class = $handlerRequest->getResourceClassName();
     $resource = new $class();
     $resource->presenter = $this;
     $this->systemContainer->callInjects($resource);
     // Prepare and order invoke parameters ---------------------------------
     $mReflection = $handlerRequest->getMethodReflection();
     $invokeParams = array();
     $requestParams = $handlerRequest->getParameters();
     $definedParams = $mReflection->getParameters();
     $index = 0;
     foreach ($definedParams as $pReflection) {
         $index++;
         // Parameter not given in URL?
         if (!isset($requestParams[$pReflection->getName()])) {
             // Default value where available
             if ($pReflection->isDefaultValueAvailable()) {
                 $invokeParams[$pReflection->getName()] = $pReflection->getDefaultValue();
                 continue;
             }
             $this->terminateWithError(self::ERROR_INVALID_REQUEST, "Missing #{$index} parameter for resource handler {$class}::" . $mReflection->getName() . '().', 400);
         }
         $invokeParams[$pReflection->getName()] = $requestParams[$pReflection->getName()];
     }
     // Perform startup
     $resource->startup();
     // Invoke handler method on resource instance
     $responsePayload = $mReflection->invokeArgs($resource, $invokeParams);
     // Automatically set HTTP 204 No Content if necessary
     if ($responsePayload === NULL && $this->httpResponse->getCode() == 200) {
         $this->httpResponse->setCode(204);
     }
     return $responsePayload === NULL ? $this->createResponse() : $this->createResponse($responsePayload);
 }