/lib/api/api_guard.rb

https://gitlab.com/beverett/gitlab-ce · Ruby · 126 lines · 87 code · 24 blank · 15 comment · 3 complexity · 77bc4ddce85a2edb7ce30e2b30a9dc36 MD5 · raw file

  1. # Guard API with OAuth 2.0 Access Token
  2. require 'rack/oauth2'
  3. module API
  4. module APIGuard
  5. extend ActiveSupport::Concern
  6. included do |base|
  7. # OAuth2 Resource Server Authentication
  8. use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
  9. # The authenticator only fetches the raw token string
  10. # Must yield access token to store it in the env
  11. request.access_token
  12. end
  13. helpers HelperMethods
  14. install_error_responders(base)
  15. end
  16. class_methods do
  17. # Set the authorization scope(s) allowed for an API endpoint.
  18. #
  19. # A call to this method maps the given scope(s) to the current API
  20. # endpoint class. If this method is called multiple times on the same class,
  21. # the scopes are all aggregated.
  22. def allow_access_with_scope(scopes, options = {})
  23. Array(scopes).each do |scope|
  24. allowed_scopes << Scope.new(scope, options)
  25. end
  26. end
  27. def allowed_scopes
  28. @scopes ||= []
  29. end
  30. end
  31. # Helper Methods for Grape Endpoint
  32. module HelperMethods
  33. include Gitlab::Auth::UserAuthFinders
  34. def find_current_user!
  35. user = find_user_from_sources
  36. return unless user
  37. forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
  38. user
  39. end
  40. def find_user_from_sources
  41. find_user_from_access_token || find_user_from_warden
  42. end
  43. private
  44. # An array of scopes that were registered (using `allow_access_with_scope`)
  45. # for the current endpoint class. It also returns scopes registered on
  46. # `API::API`, since these are meant to apply to all API routes.
  47. def scopes_registered_for_endpoint
  48. @scopes_registered_for_endpoint ||=
  49. begin
  50. endpoint_classes = [options[:for].presence, ::API::API].compact
  51. endpoint_classes.reduce([]) do |memo, endpoint|
  52. if endpoint.respond_to?(:allowed_scopes)
  53. memo.concat(endpoint.allowed_scopes)
  54. else
  55. memo
  56. end
  57. end
  58. end
  59. end
  60. end
  61. module ClassMethods
  62. private
  63. def install_error_responders(base)
  64. error_classes = [Gitlab::Auth::MissingTokenError,
  65. Gitlab::Auth::TokenNotFoundError,
  66. Gitlab::Auth::ExpiredError,
  67. Gitlab::Auth::RevokedError,
  68. Gitlab::Auth::InsufficientScopeError]
  69. base.__send__(:rescue_from, *error_classes, oauth2_bearer_token_error_handler) # rubocop:disable GitlabSecurity/PublicSend
  70. end
  71. def oauth2_bearer_token_error_handler
  72. proc do |e|
  73. response =
  74. case e
  75. when Gitlab::Auth::MissingTokenError
  76. Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
  77. when Gitlab::Auth::TokenNotFoundError
  78. Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
  79. :invalid_token,
  80. "Bad Access Token.")
  81. when Gitlab::Auth::ExpiredError
  82. Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
  83. :invalid_token,
  84. "Token is expired. You can either do re-authorization or token refresh.")
  85. when Gitlab::Auth::RevokedError
  86. Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
  87. :invalid_token,
  88. "Token was revoked. You have to re-authorize from the user.")
  89. when Gitlab::Auth::InsufficientScopeError
  90. # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
  91. # does not include WWW-Authenticate header, which breaks the standard.
  92. Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
  93. :insufficient_scope,
  94. Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
  95. { scope: e.scopes })
  96. end
  97. response.finish
  98. end
  99. end
  100. end
  101. end
  102. end