Merge branch 'private-doorkeeper' into develop

# Conflicts:
#	app/assets/javascripts/app/controllers/api.coffee
This commit is contained in:
Martin Edenhofer 2016-11-14 00:12:55 +01:00
commit 972fb75dfb
12 changed files with 507 additions and 26 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
@ -366,6 +368,7 @@ DEPENDENCIES
daemons
delayed_job_active_record
diffy
doorkeeper
eco
em-websocket
email_verifier

View file

@ -78,14 +78,15 @@ class App.ControllerTable extends App.Controller
e.preventDefault()
console.log('checkboxClick', e.target)
callbackHeader = (header) ->
console.log('current header is', header)
callbackHeader = (headers) ->
console.log('current header is', headers)
# add new header item
attribute =
name: 'some name'
display: 'Some Name'
header.push attribute
console.log('new header is', header)
headers.push attribute
console.log('new header is', headers)
headers
callbackAttributes = (value, object, attribute, header, refObject) ->
console.log('data of item col', value, object, attribute, header, refObject)

View file

@ -34,20 +34,47 @@ class Index extends App.ControllerSubContent
App.Setting.unsubscribe(@subscribeApplicationId)
table = =>
callbackHeader = (headers) ->
attribute =
name: 'view'
display: 'View'
headers.splice(3, 0, attribute)
attribute =
name: 'token'
display: 'Token'
headers.splice(4, 0, attribute)
headers
callbackViewAttributes = (value, object, attribute, header, refObject) ->
value = 'X'
value
callbackTokenAttributes = (value, object, attribute, header, refObject) ->
value = 'X'
value
new App.ControllerTable(
el: @$('.js-appList')
model: App.Application
tableId: 'applications'
objects: App.Application.all()
el: @$('.js-appList')
model: App.Application
tableId: 'applications'
objects: App.Application.all()
bindRow:
events:
'click': @appEdit
bindCol:
view:
events:
'click': @appView
token:
events:
'click': @appToken
callbackHeader: [callbackHeader]
callbackAttributes:
view: [callbackViewAttributes]
token: [callbackTokenAttributes]
)
table()
#App.Application.fetchFull(
# table
# clear: true
#)
@subscribeApplicationId = App.Application.subscribe(table, initFetch: true, clear: true)
@ -82,6 +109,18 @@ class Index extends App.ControllerSubContent
value = @PasswordAccess.prop('checked')
App.Setting.set('api_password_access', value)
appToken: (id, e) ->
e.preventDefault()
new ViewAppTokenModal(
app: App.Application.find(id)
)
appView: (id, e) ->
e.preventDefault()
new ViewAppModal(
app: App.Application.find(id)
)
appNew: (e) ->
e.preventDefault()
new App.ControllerGenericNew(
@ -107,4 +146,51 @@ class Index extends App.ControllerSubContent
container: @el.closest('.content')
)
class ViewAppModal extends App.ControllerModal
headPrefix: 'App'
buttonSubmit: false
buttonCancel: true
shown: true
small: true
events:
'click .js-select': 'selectAll'
constructor: (params) ->
@head = params.app.name
super
content: ->
"AppID: <input class=\"js-select\" type=\"text\" value=\"#{@app.uid}\">
<br>
Secret: <input class=\"js-select\" type=\"text\" value=\"#{@app.secret}\">"
class ViewAppTokenModal extends App.ControllerModal
headPrefix: 'Generate Token'
buttonSubmit: 'Generate Token'
buttonCancel: true
shown: true
small: true
events:
'click .js-select': 'selectAll'
constructor: (params) ->
@head = params.app.name
super
content: ->
"#{App.i18n.translateContent('Generate Access Token for |%s|', App.Session.get().displayNameLong())}"
onSubmit: =>
@ajax(
id: 'application_token'
type: 'POST'
url: "#{@apiPath}/applications/token"
processData: true
data: JSON.stringify(id: @app.id)
success: (data, status, xhr) =>
@contentInline = "#{App.i18n.translateContent('New Access Token is')}: <input class=\"js-select\" type=\"text\" value=\"#{data.token}\">"
@update()
@$('.js-submit').remove()
)
App.Config.set('API', { prio: 1200, name: 'API', parent: '#system', target: '#system/api', controller: Index, permission: ['admin.api'] }, 'NavBarAdmin')

View file

@ -1,17 +1,16 @@
class App.Application extends App.Model
@configure 'Application', 'name', 'scopes', 'redirect_uri'
@configure 'Application', 'name', 'redirect_uri'
@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: 'redirect_uri', display: 'Callback URL', tag: 'textarea', limit: 250, null: false, note: 'Use one line per URI' },
{ name: 'clients', display: 'Clients', tag: 'input', readonly: 1 },
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
]
@configure_overview = [
'name', 'scopes', 'clients'
'name', 'redirect_uri', 'clients'
]
@configure_delete = true

View file

@ -51,6 +51,7 @@ curl -u <%= @S('email') %>:some_password <%= @C('http_type') %>://<%= @C('fqdn')
<button class="btn js-appNew"><%- @T('New Application') %></button>
<br>
<br>
<div>
@ -59,7 +60,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 +70,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
@ -309,19 +308,23 @@ class ApplicationController < ActionController::Base
logger.debug "oauth2 token auth check '#{token}'"
access_token = Doorkeeper::AccessToken.by_token(token)
if !access_token
raise Exceptions::NotAuthorized, 'Invalid token!'
end
# check expire
if access_token.expires_in && (access_token.created_at + access_token.expires_in) < Time.zone.now
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,72 @@
# Copyright (C) 2012-2016 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
application = item.attributes
application[:clients] = Doorkeeper::AccessToken.where(application_id: item.id).count
assets[:Application][item.id] = application
}
render json: {
record_ids: item_ids,
assets: assets,
}, status: :ok
return
end
render json: all, status: :ok
end
def token
access_token = Doorkeeper::AccessToken.create!(application_id: params[:id], resource_owner_id: current_user.id)
render json: { token: access_token.token }, 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.delete('updated_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,14 @@
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
match api_path + '/applications/token', to: 'applications#token', via: :post
# 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