-
Notifications
You must be signed in to change notification settings - Fork 0
Api key implementation #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: DioFun <DioFun@users.noreply.github.com> Co-authored-by: Nymous <thomas.gaudin@centraliens-lille.org> Co-authored-by: AliasXoX <AliasXoX@users.noreply.github.com> Co-authored-by: Molymawk <Molymawk@users.noreply.github.com>
def initialize(api_key) | ||
return if api_key.blank? | ||
|
||
can :read, :all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need in a near future to refine the permissions of each key, maybe by listing all the endpoints when creating one and let the user select which he wants the bearer to access (read, create, update, destroy), and change the permissions in the edit view.
|
||
def index | ||
@api_keys = ApiKey.accessible_by(current_ability) | ||
authorize! :index, @api_keys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: differs from how we manage index in user_controller
|
||
private | ||
|
||
def still_authenticated? | ||
log_out if should_log_out? | ||
end | ||
|
||
def api_auth | ||
return unless request.path[0, 5] == '/api/' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely useless comment, but there is hard-coded text right here
app/helpers/sessions_helper.rb
Outdated
def log_in_api(bearer) | ||
reset_session # For security reasons, we clear the session data before login | ||
session[:api_key_id] = bearer.id | ||
session[:expires_at] = Time.current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't understand why it's done this way
|
||
def current_ability | ||
@current_ability ||= if !session[:api_key_id].nil? | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1,6 +1,7 @@ | |||
# frozen_string_literal: true | |||
|
|||
class SessionsController < ApplicationController | |||
include ApiKeyAuthenticatable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
include ApiKeyAuthenticatable |
I'm not sure this is still useful
config/initializers/_constants.rb
Outdated
# Path to login via api key | ||
AUTH_API_PATH = '/auth/api' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Path to login via api key | |
AUTH_API_PATH = '/auth/api' |
I think it's an artifact of the previous version that uses full connection and not Authorization header
config/routes.rb
Outdated
resources :users, controller: :users, as: 'api_users' | ||
resources :api_keys, controller: :api_keys, as: 'api_api_keys' | ||
resources :machines, controller: :machines, as: 'api_machines' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely things to do about these endpoints but that's not the goal of this PR
@api_key = api_keys(:FakeRadius) | ||
@real_key = '5fcdb374f0a70e9ff0675a0ce4acbdf6d21225fe74483319c2766074732d6d80' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe test if a wrong api key is provided ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great PR! Limited in scope, with tests, docs and example sources...
I left some comments on the files, and maybe a more "general" thing to consider:
Maybe we should rework a little bit the authenticate_with_http_token
and its callback authenticator
, which doesn't have a !
but raises an exception if the API key is not found, but because we only use authenticate_with_http_token
and not authenticate_or_request_with_http_token
Rails doesn't return a 401 if the API key is wrong, it's just a 404... Maybe get a bit closer from the example, where they use the or_request
method, and the find_by
(without !
) to return nil if no API key matches instead of an exception, that way we have proper HTTP status codes (and we can always implement the "optional" auth if needed...)
class ApiKeysController < ApiApplicationController | ||
def index | ||
@api_keys = ApiKey.accessible_by(current_ability) | ||
authorize! :index, @api_keys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure you can call authorize! :index
with a list of objects, or if it works like you expect. Can you try to remove the permissions in your ability.rb and make sure this breaks as expected?
(But if it works, great! We will add it to the other "index" actions in all controllers ^^)
format.html { render 'new', status: :unprocessable_entity } | ||
end | ||
if @api_key.save | ||
flash[:new_api_key] = "ApiKey a dded! It is #{@api_key.api_key}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you specify the entire flash text, I think you can keep the :success
key. I was thinking of using a custom flash key to allow for a specific presentation in the view, where we show the API key in a code block.
assert_raises CanCan::AccessDenied do | ||
get "#{api_api_keys_path}.json" | ||
end | ||
get "#{api_api_keys_path}.json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can now remove these .json in paths, with the default fomat: :json
!
MOD_DIRECTORY = 'app/controllers/api/' | ||
MOD_CONTROLLER = 'ApiApplicationController' | ||
MSG = "All controllers in the Mod namespace should inherit from #{MOD_CONTROLLER}" # rubocop:disable Style/MutableConstant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MOD_DIRECTORY = 'app/controllers/api/' | |
MOD_CONTROLLER = 'ApiApplicationController' | |
MSG = "All controllers in the Mod namespace should inherit from #{MOD_CONTROLLER}" # rubocop:disable Style/MutableConstant | |
API_DIRECTORY = 'app/controllers/api/' | |
API_CONTROLLER = 'ApiApplicationController' | |
MSG = "All controllers in the Api namespace should inherit from #{API_CONTROLLER}".freeze |
parent_class = node.parent_class&.const_name || '' | ||
|
||
if parent_class == MOD_CONTROLLER || | ||
(parent_module_name.split('::').include?('Mod') && "Mod::#{parent_class}" == MOD_CONTROLLER) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(parent_module_name.split('::').include?('Mod') && "Mod::#{parent_class}" == MOD_CONTROLLER) | |
(parent_module_name.split('::').include?('Api') && "Api::#{parent_class}" == MOD_CONTROLLER) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's gooooo
@@ -107,4 +110,8 @@ def setup | |||
test 'admin can do everything' do | |||
assert @admin_ability.can?(:manage, :all) | |||
end | |||
|
|||
test 'shoul be able to read everything with an api key' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo shoul
instead of should
This pull request implements a new authentication method for lea5 using API Keys.
The authentication procedure using an API key has been documented.
Here's an overview of the changes coming with this pull request :
A new rails concern to authorize API key authentication (see api_key_authenticatable.rb)The method of implemention is highly inspired by https://keygen.sh/blog/how-to-implement-api-key-authentication-in-rails-without-devise/