From e41dadcb33fda44ee274daa673bd933e13aa90eb Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 19 Dec 2014 16:15:29 +0200 Subject: Doorkeeper integration --- lib/api/api_guard.rb | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 lib/api/api_guard.rb (limited to 'lib/api/api_guard.rb') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb new file mode 100644 index 00000000000..23975518181 --- /dev/null +++ b/lib/api/api_guard.rb @@ -0,0 +1,175 @@ +# Guard API with OAuth 2.0 Access Token + +require 'rack/oauth2' + +module APIGuard + extend ActiveSupport::Concern + + included do |base| + # OAuth2 Resource Server Authentication + use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request| + # The authenticator only fetches the raw token string + + # Must yield access token to store it in the env + request.access_token + end + + helpers HelperMethods + + install_error_responders(base) + end + + # Helper Methods for Grape Endpoint + module HelperMethods + # Invokes the doorkeeper guard. + # + # If token is presented and valid, then it sets @current_user. + # + # If the token does not have sufficient scopes to cover the requred scopes, + # then it raises InsufficientScopeError. + # + # If the token is expired, then it raises ExpiredError. + # + # If the token is revoked, then it raises RevokedError. + # + # If the token is not found (nil), then it raises TokenNotFoundError. + # + # Arguments: + # + # scopes: (optional) scopes required for this guard. + # Defaults to empty array. + # + def doorkeeper_guard!(scopes: []) + if (access_token = find_access_token).nil? + raise TokenNotFoundError + + else + case validate_access_token(access_token, scopes) + when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE + raise InsufficientScopeError.new(scopes) + + when Oauth2::AccessTokenValidationService::EXPIRED + raise ExpiredError + + when Oauth2::AccessTokenValidationService::REVOKED + raise RevokedError + + when Oauth2::AccessTokenValidationService::VALID + @current_user = User.find(access_token.resource_owner_id) + + end + end + end + + def doorkeeper_guard(scopes: []) + if access_token = find_access_token + case validate_access_token(access_token, scopes) + when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE + raise InsufficientScopeError.new(scopes) + + when Oauth2::AccessTokenValidationService::EXPIRED + raise ExpiredError + + when Oauth2::AccessTokenValidationService::REVOKED + raise RevokedError + + when Oauth2::AccessTokenValidationService::VALID + @current_user = User.find(access_token.resource_owner_id) + end + end + end + + def current_user + @current_user + end + + private + def find_access_token + @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) + end + + def doorkeeper_request + @doorkeeper_request ||= ActionDispatch::Request.new(env) + end + + def validate_access_token(access_token, scopes) + Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes) + end + end + + module ClassMethods + # Installs the doorkeeper guard on the whole Grape API endpoint. + # + # Arguments: + # + # scopes: (optional) scopes required for this guard. + # Defaults to empty array. + # + def guard_all!(scopes: []) + before do + guard! scopes: scopes + end + end + + private + def install_error_responders(base) + error_classes = [ MissingTokenError, TokenNotFoundError, + ExpiredError, RevokedError, InsufficientScopeError] + + base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler + end + + def oauth2_bearer_token_error_handler + Proc.new {|e| + response = case e + when MissingTokenError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new + + when TokenNotFoundError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :invalid_token, + "Bad Access Token.") + + when ExpiredError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :invalid_token, + "Token is expired. You can either do re-authorization or token refresh.") + + when RevokedError + Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new( + :invalid_token, + "Token was revoked. You have to re-authorize from the user.") + + when InsufficientScopeError + # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2) + # does not include WWW-Authenticate header, which breaks the standard. + Rack::OAuth2::Server::Resource::Bearer::Forbidden.new( + :insufficient_scope, + Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope], + { :scope => e.scopes}) + end + + response.finish + } + end + end + + # + # Exceptions + # + + class MissingTokenError < StandardError; end + + class TokenNotFoundError < StandardError; end + + class ExpiredError < StandardError; end + + class RevokedError < StandardError; end + + class InsufficientScopeError < StandardError + attr_reader :scopes + def initialize(scopes) + @scopes = scopes + end + end +end \ No newline at end of file -- cgit v1.2.1 From 4f1d1fc51baf396d49f6b159c84e15194706847c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 Feb 2015 19:30:09 -0800 Subject: Convert hashes to ruby 1.9 style --- lib/api/api_guard.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api/api_guard.rb') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 23975518181..28765b142f6 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -146,7 +146,7 @@ module APIGuard Rack::OAuth2::Server::Resource::Bearer::Forbidden.new( :insufficient_scope, Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope], - { :scope => e.scopes}) + { scope: e.scopes}) end response.finish @@ -172,4 +172,4 @@ module APIGuard @scopes = scopes end end -end \ No newline at end of file +end -- cgit v1.2.1 From e89058268118e3b2be4ebaf5d7bf2c684b590437 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 Feb 2015 20:36:54 -0800 Subject: Rubocop enabled for: Use spaces inside hash literal braces --- lib/api/api_guard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/api/api_guard.rb') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 28765b142f6..be3d053efca 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -146,7 +146,7 @@ module APIGuard Rack::OAuth2::Server::Resource::Bearer::Forbidden.new( :insufficient_scope, Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope], - { scope: e.scopes}) + { scope: e.scopes }) end response.finish -- cgit v1.2.1 From da884aabc7674c68eee23699efb357bc94aef8d3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 Feb 2015 21:22:57 -0800 Subject: Avoid using {...} for multi-line blocks --- lib/api/api_guard.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/api/api_guard.rb') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index be3d053efca..cb20bf0720d 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -120,7 +120,7 @@ module APIGuard end def oauth2_bearer_token_error_handler - Proc.new {|e| + Proc.new do |e| response = case e when MissingTokenError Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new @@ -150,7 +150,7 @@ module APIGuard end response.finish - } + end end end -- cgit v1.2.1 From 368e9a0862dd7d58b009956e8f1ac51d2a549cda Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 2 Feb 2015 21:26:40 -0800 Subject: Rubocop: Style/CaseIndentation enabled --- lib/api/api_guard.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'lib/api/api_guard.rb') diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index cb20bf0720d..b9994fcefda 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -47,16 +47,12 @@ module APIGuard case validate_access_token(access_token, scopes) when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE raise InsufficientScopeError.new(scopes) - when Oauth2::AccessTokenValidationService::EXPIRED raise ExpiredError - when Oauth2::AccessTokenValidationService::REVOKED raise RevokedError - when Oauth2::AccessTokenValidationService::VALID @current_user = User.find(access_token.resource_owner_id) - end end end @@ -121,7 +117,8 @@ module APIGuard def oauth2_bearer_token_error_handler Proc.new do |e| - response = case e + response = + case e when MissingTokenError Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new -- cgit v1.2.1