Working on OAuth2 support with doorkeeper gem.

This commit is contained in:
Thorsten Eckel 2016-11-01 17:16:04 +01:00
parent 4e84b60445
commit faafe0cc60
10 changed files with 394 additions and 13 deletions

View file

@ -28,6 +28,7 @@ end
gem 'autoprefixer-rails'
gem 'doorkeeper'
gem 'oauth2'
gem 'omniauth'

View file

@ -82,6 +82,8 @@ GEM
docile (1.1.5)
domain_name (0.5.20160826)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.0)
railties (>= 4.2)
eco (1.0.0)
coffee-script
eco-source
@ -350,6 +352,7 @@ DEPENDENCIES
daemons
delayed_job_active_record
diffy
doorkeeper
eco
em-websocket
email_verifier

View file

@ -1,17 +1,17 @@
class App.Application extends App.Model
@configure 'Application', 'name', 'scopes', 'redirect_uri'
@configure 'Application', 'name', 'redirect_uri', 'uid', 'secret'
@extend Spine.Model.Ajax
@url: @apiPath + '/applications'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'redirect_uri', display: 'Redirect URI', tag: 'textarea', limit: 250, null: false, note: 'Use one line per URI' },
{ name: 'scopes', display: 'Scopes', tag: 'input', note: 'Scopes define the access for' },
{ name: 'clients', display: 'Clients', tag: 'input', readonly: 1 },
{ name: 'uid', display: 'Application ID', tag: 'input', type: 'text', null: true, readonly: 1 },
{ name: 'secret', display: 'Application secret', tag: 'input', type: 'text', null: true },
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
]
@configure_overview = [
'name', 'scopes', 'clients'
'name', 'uid'
]
@configure_delete = true

View file

@ -59,7 +59,7 @@ OAuth URLs are:
<table class="settings-list" style="width: 100%;">
<thead>
<tr>
<th width="40%"><%- @T('Title') %>
<th width="40%"><%- @T('Action') %>
<th width="60%"><%- @T('URL') %>
</thead>
<tbody>
@ -69,9 +69,6 @@ OAuth URLs are:
<tr>
<td><%- @T('Getting an Access Token') %>
<td><%= @C('http_type') %>://<%= @C('fqdn') %>/oauth/token
<tr>
<td><%- @T('Revoking Access') %>
<td><%= @C('http_type') %>://<%= @C('fqdn') %>/oauth/applications
</tbody>
</table>
</div>

View file

@ -301,7 +301,6 @@ class ApplicationController < ActionController::Base
return authentication_check_prerequesits(user, 'token_auth', auth_param) if user
end
=begin
# check oauth2 token based authentication
token = Doorkeeper::OAuth::Token.from_bearer_authorization(request)
if token
@ -314,14 +313,14 @@ class ApplicationController < ActionController::Base
raise Exceptions::NotAuthorized, 'OAuth2 token is expired!'
end
if access_token.scopes.empty?
raise Exceptions::NotAuthorized, 'OAuth2 scope missing for token!'
end
# if access_token.scopes.empty?
# raise Exceptions::NotAuthorized, 'OAuth2 scope missing for token!'
# end
user = User.find(access_token.resource_owner_id)
return authentication_check_prerequesits(user, 'token_auth', auth_param) if user
end
=end
false
end

View file

@ -0,0 +1,64 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class ApplicationsController < ApplicationController
before_action { authentication_check(permission: 'admin.api') }
def index
all = Doorkeeper::Application.all
if params[:full]
assets = {}
item_ids = []
all.each { |item|
item_ids.push item.id
if !assets[:Application]
assets[:Application] = {}
end
assets[:Application][item.id] = item.attributes
}
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
render json: all, status: :ok
end
def show
application = Doorkeeper::Application.find(params[:id])
render json: application, status: :ok
end
def create
application = Doorkeeper::Application.new(clean_params)
application.save!
render json: application, status: :ok
end
def update
application = Doorkeeper::Application.find(params[:id])
application.update_attributes!(clean_params)
render json: application, status: :ok
end
def destroy
application = Doorkeeper::Application.find(params[:id])
application.destroy!
render json: {}, status: :ok
end
private
def clean_params
params_data = params.permit! #.to_h
params_data.delete('application')
params_data.delete('action')
params_data.delete('controller')
params_data.delete('id')
params_data.delete('uid')
params_data.delete('secret')
params_data.delete('created_at')
params_data
end
end

View file

@ -0,0 +1,112 @@
Doorkeeper.configure do
# Change the ORM that doorkeeper will use (needs plugins)
orm :active_record
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# fail "Please configure doorkeeper resource_owner_authenticator block located in #{__FILE__}"
# Put your resource owner authentication logic here.
# Example implementation:
User.find_by_id(session[:user_id]) || redirect_to(new_user_session_url)
end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
# admin_authenticator do
# # Put your admin authentication logic here.
# # Example implementation:
# Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
# end
# Authorization Code expiration time (default 10 minutes).
# authorization_code_expires_in 10.minutes
# Access token expiration time (default 2 hours).
# If you want to disable expiration, set this to nil.
# access_token_expires_in 2.hours
# Assign a custom TTL for implicit grants.
# custom_access_token_expires_in do |oauth_client|
# oauth_client.application.additional_settings.implicit_oauth_expiration
# end
# Use a custom class for generating the access token.
# https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
# access_token_generator '::Doorkeeper::JWT'
# The controller Doorkeeper::ApplicationController inherits from.
# Defaults to ActionController::Base.
# https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller
# base_controller 'ApplicationController'
# Reuse access token for the same resource owner within an application (disabled by default)
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
# reuse_access_token
# Issue access tokens with refresh token (disabled by default)
# use_refresh_token
# Provide support for an owner to be assigned to each registered application (disabled by default)
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
# a registered application
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
# enable_application_owner confirmation: false
# Define access token scopes for your provider
# For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
# default_scopes :public
# optional_scopes :write, :update
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
# falls back to the `:client_id` and `:client_secret` params from the `params` object.
# Check out the wiki for more information on customization
# client_credentials :from_basic, :from_params
# Change the way access token is authenticated from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
# falls back to the `:access_token` or `:bearer_token` params from the `params` object.
# Check out the wiki for more information on customization
# access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
# Change the native redirect uri for client apps
# When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider
# The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
# (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
#
# native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
# Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
# by default in non-development environments). OAuth2 delegates security in
# communication to the HTTPS protocol so it is wise to keep this enabled.
#
# force_ssl_in_redirect_uri !Rails.env.development?
# Specify what grant flows are enabled in array of Strings. The valid
# strings and the flows they enable are:
#
# "authorization_code" => Authorization Code Grant Flow
# "implicit" => Implicit Grant Flow
# "password" => Resource Owner Password Credentials Grant Flow
# "client_credentials" => Client Credentials Grant Flow
#
# If not specified, Doorkeeper enables authorization_code and
# client_credentials.
#
# implicit and password grant flows have risks that you should understand
# before enabling:
# http://tools.ietf.org/html/rfc6819#section-4.4.2
# http://tools.ietf.org/html/rfc6819#section-4.4.3
#
# grant_flows %w(authorization_code client_credentials)
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
# For example if dealing with a trusted application.
# skip_authorization do |resource_owner, client|
# client.superapp? or resource_owner.admin?
# end
# WWW-Authenticate Realm (default "Doorkeeper").
# realm "Doorkeeper"
end

View file

@ -0,0 +1,124 @@
en:
activerecord:
attributes:
doorkeeper/application:
name: 'Name'
redirect_uri: 'Redirect URI'
errors:
models:
doorkeeper/application:
attributes:
redirect_uri:
fragment_present: 'cannot contain a fragment.'
invalid_uri: 'must be a valid URI.'
relative_uri: 'must be an absolute URI.'
secured_uri: 'must be an HTTPS/SSL URI.'
doorkeeper:
applications:
confirmations:
destroy: 'Are you sure?'
buttons:
edit: 'Edit'
destroy: 'Destroy'
submit: 'Submit'
cancel: 'Cancel'
authorize: 'Authorize'
form:
error: 'Whoops! Check your form for possible errors'
help:
redirect_uri: 'Use one line per URI'
native_redirect_uri: 'Use %{native_redirect_uri} for local tests'
scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.'
edit:
title: 'Edit application'
index:
title: 'Your applications'
new: 'New Application'
name: 'Name'
callback_url: 'Callback URL'
new:
title: 'New Application'
show:
title: 'Application: %{name}'
application_id: 'Application Id'
secret: 'Secret'
scopes: 'Scopes'
callback_urls: 'Callback urls'
actions: 'Actions'
authorizations:
buttons:
authorize: 'Authorize'
deny: 'Deny'
error:
title: 'An error has occurred'
new:
title: 'Authorization required'
prompt: 'Authorize %{client_name} to use your account?'
able_to: 'This application will be able to'
show:
title: 'Authorization code'
authorized_applications:
confirmations:
revoke: 'Are you sure?'
buttons:
revoke: 'Revoke'
index:
title: 'Your authorized applications'
application: 'Application'
created_at: 'Created At'
date_format: '%Y-%m-%d %H:%M:%S'
errors:
messages:
# Common error messages
invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
invalid_redirect_uri: 'The redirect uri included is not valid.'
unauthorized_client: 'The client is not authorized to perform this request using this method.'
access_denied: 'The resource owner or authorization server denied the request.'
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
#configuration error messages
credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'
# Access grant errors
unsupported_response_type: 'The authorization server does not support this response type.'
# Access token errors
invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
# Password Access token errors
invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found'
invalid_token:
revoked: "The access token was revoked"
expired: "The access token expired"
unknown: "The access token is invalid"
flash:
applications:
create:
notice: 'Application created.'
destroy:
notice: 'Application deleted.'
update:
notice: 'Application updated.'
authorized_applications:
destroy:
notice: 'Application revoked.'
layouts:
admin:
nav:
oauth2_provider: 'OAuth2 Provider'
applications: 'Applications'
home: 'Home'
application:
title: 'OAuth authorization required'

View file

@ -0,0 +1,13 @@
Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
match api_path + '/applications', to: 'applications#index', via: :get
match api_path + '/applications/:id', to: 'applications#show', via: :get
match api_path + '/applications', to: 'applications#create', via: :post
match api_path + '/applications/:id', to: 'applications#update', via: :put
# oauth2 provider routes
use_doorkeeper do
skip_controllers :applications, :authorized_applications
end
end

View file

@ -0,0 +1,68 @@
class CreateDoorkeeperTables < ActiveRecord::Migration
def change
create_table :oauth_applications do |t|
t.string :name, null: false
t.string :uid, null: false
t.string :secret, null: false
t.text :redirect_uri, null: false
t.string :scopes, null: false, default: ''
t.timestamps null: false
end
add_index :oauth_applications, :uid, unique: true
create_table :oauth_access_grants do |t|
t.integer :resource_owner_id, null: false
t.references :application, null: false
t.string :token, null: false
t.integer :expires_in, null: false
t.text :redirect_uri, null: false
t.datetime :created_at, null: false
t.datetime :revoked_at
t.string :scopes
end
add_index :oauth_access_grants, :token, unique: true
add_foreign_key(
:oauth_access_grants,
:oauth_applications,
column: :application_id
)
create_table :oauth_access_tokens do |t|
t.integer :resource_owner_id
t.references :application
# If you use a custom token generator you may need to change this column
# from string to text, so that it accepts tokens larger than 255
# characters. More info on custom token generators in:
# https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
#
# t.text :token, null: false
t.string :token, null: false
t.string :refresh_token
t.integer :expires_in
t.datetime :revoked_at
t.datetime :created_at, null: false
t.string :scopes
# If there is a previous_refresh_token column,
# refresh tokens will be revoked after a related access token is used.
# If there is no previous_refresh_token column,
# previous tokens are revoked as soon as a new access token is created.
# Comment out this line if you'd rather have refresh tokens
# instantly revoked.
t.string :previous_refresh_token, null: false, default: ''
end
add_index :oauth_access_tokens, :token, unique: true
add_index :oauth_access_tokens, :resource_owner_id
add_index :oauth_access_tokens, :refresh_token, unique: true
add_foreign_key(
:oauth_access_tokens,
:oauth_applications,
column: :application_id
)
end
end