Commit e41dadcb authored by Valery Sizov's avatar Valery Sizov

Doorkeeper integration

parent 5cf2bd4c
......@@ -29,6 +29,8 @@ gem 'omniauth-twitter'
gem 'omniauth-github'
gem 'omniauth-shibboleth'
gem 'omniauth-kerberos'
gem 'doorkeeper', '2.0.1'
gem "rack-oauth2", "~> 1.0.5"
# Extracting information from a git repository
# Provide access to Gitlab::Git library
......
......@@ -37,6 +37,7 @@ GEM
rake (>= 0.8.7)
arel (5.0.1.20140414130214)
asciidoctor (0.1.4)
attr_required (1.0.0)
awesome_print (1.2.0)
axiom-types (0.0.5)
descendants_tracker (~> 0.0.1)
......@@ -107,6 +108,8 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.3)
docile (1.1.5)
doorkeeper (2.0.1)
railties (>= 3.1)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
......@@ -250,6 +253,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpauth (0.2.1)
httpclient (2.5.3.3)
i18n (0.6.11)
ice_nine (0.10.0)
jasmine (2.0.2)
......@@ -368,6 +372,12 @@ GEM
rack (>= 1.1.3)
rack-mount (0.8.3)
rack (>= 1.0.0)
rack-oauth2 (1.0.8)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
httpclient (>= 2.2.0.2)
multi_json (>= 1.3.6)
rack (>= 1.1)
rack-protection (1.5.1)
rack
rack-test (0.6.2)
......@@ -616,6 +626,7 @@ DEPENDENCIES
devise (= 3.2.4)
devise-async (= 0.9.0)
diffy (~> 3.0.3)
doorkeeper (= 2.0.1)
dropzonejs-rails
email_spec
enumerize
......@@ -672,6 +683,7 @@ DEPENDENCIES
rack-attack
rack-cors
rack-mini-profiler
rack-oauth2 (~> 1.0.5)
rails (~> 4.1.0)
rails_autolink (~> 1.1)
rails_best_practices
......
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_filter :authenticate_user!
layout "profile"
def index
@applications = current_user.oauth_applications
end
def create
@application = Doorkeeper::Application.new(application_params)
@application.owner = current_user if Doorkeeper.configuration.confirm_application_owner?
if @application.save
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
redirect_to oauth_application_url(@application)
else
render :new
end
end
def destroy
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy]) if @application.destroy
redirect_to profile_account_url
end
end
\ No newline at end of file
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_filter :authenticate_resource_owner!
layout "profile"
def new
if pre_auth.authorizable?
if skip_authorization? || matching_token?
auth = authorization.authorize
redirect_to auth.redirect_uri
else
render "doorkeeper/authorizations/new"
end
else
render "doorkeeper/authorizations/error"
end
end
# TODO: Handle raise invalid authorization
def create
redirect_or_render authorization.authorize
end
def destroy
redirect_or_render authorization.deny
end
private
def matching_token?
Doorkeeper::AccessToken.matching_token_for pre_auth.client,
current_resource_owner.id,
pre_auth.scopes
end
def redirect_or_render(auth)
if auth.redirectable?
redirect_to auth.redirect_uri
else
render json: auth.body, status: auth.status
end
end
def pre_auth
@pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
server.client_via_uid,
params)
end
def authorization
@authorization ||= strategy.request
end
def strategy
@strategy ||= server.authorization_request pre_auth.response_type
end
end
class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
layout "profile"
def destroy
Doorkeeper::AccessToken.revoke_all_for params[:id], current_resource_owner
redirect_to profile_account_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
end
end
\ No newline at end of file
......@@ -3,5 +3,7 @@ class Profiles::AccountsController < ApplicationController
def show
@user = current_user
@applications = current_user.oauth_applications
@authorized_applications = Doorkeeper::Application.authorized_for(current_user)
end
end
......@@ -106,6 +106,7 @@ class User < ActiveRecord::Base
has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
#
......
module Oauth2::AccessTokenValidationService
# Results:
VALID = :valid
EXPIRED = :expired
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
class << self
def validate(token, scopes: [])
if token.expired?
return EXPIRED
elsif token.revoked?
return REVOKED
elsif !self.sufficent_scope?(token, scopes)
return INSUFFICIENT_SCOPE
else
return VALID
end
end
protected
# True if the token's scope is a superset of required scopes,
# or the required scopes is empty.
def sufficent_scope?(token, scopes)
if scopes.blank?
# if no any scopes required, the scopes of token is sufficient.
return true
else
# If there are scopes required, then check whether
# the set of authorized scopes is a superset of the set of required scopes
required_scopes = Set.new(scopes)
authorized_scopes = Set.new(token.scopes)
return authorized_scopes >= required_scopes
end
end
end
end
\ No newline at end of file
- submit_btn_css ||= 'btn btn-link btn-remove btn-small'
= form_tag oauth_application_path(application) do
%input{:name => "_method", :type => "hidden", :value => "delete"}/
= submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css
\ No newline at end of file
= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f|
- if application.errors.any?
.alert.alert-danger{"data-alert" => ""}
%p Whoops! Check your form for possible errors
= content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do
= f.label :name, class: 'col-sm-2 control-label'
.col-sm-10
= f.text_field :name, class: 'form-control'
= doorkeeper_errors_for application, :name
= content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do
= f.label :redirect_uri, class: 'col-sm-2 control-label'
.col-sm-10
= f.text_area :redirect_uri, class: 'form-control'
= doorkeeper_errors_for application, :redirect_uri
%span.help-block
Use one line per URI
- if Doorkeeper.configuration.native_redirect_uri
%span.help-block
Use
%code= Doorkeeper.configuration.native_redirect_uri
for local tests
.form-group
.col-sm-offset-2.col-sm-10
= f.submit 'Submit', class: "btn btn-primary wide"
= link_to "Cancel", profile_account_path, :class => "btn btn-default"
\ No newline at end of file
%h3.page-title Edit application
= render 'form', application: @application
\ No newline at end of file
%h3.page-title Your applications
%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
%table.table.table-striped
%thead
%tr
%th Name
%th Callback URL
%th
%th
%tbody
- @applications.each do |application|
%tr{:id => "application_#{application.id}"}
%td= link_to application.name, oauth_application_path(application)
%td= application.redirect_uri
%td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
%td= render 'delete_form', application: application
\ No newline at end of file
%h3.page-title New application
= render 'form', application: @application
\ No newline at end of file
%h3.page-title
Application: #{@application.name}
.row
.col-md-8
%h4 Application Id:
%p
%code#application_id= @application.uid
%h4 Secret:
%p
%code#secret= @application.secret
%h4 Callback urls:
%table
- @application.redirect_uri.split.each do |uri|
%tr
%td
%code= uri
%td
= link_to 'Authorize', oauth_authorization_path(client_id: @application.uid, redirect_uri: uri, response_type: 'code'), class: 'btn btn-success', target: '_blank'
.prepend-top-20
%p= link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
%p= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
\ No newline at end of file
%h3.page-title An error has occurred
%main{:role => "main"}
%pre= @pre_auth.error_response.body[:error_description]
\ No newline at end of file
%h3.page-title Authorize required
%main{:role => "main"}
%p.h4
Authorize
%strong.text-info= @pre_auth.client.name
to use your account?
- if @pre_auth.scopes
#oauth-permissions
%p This application will be able to:
%ul.text-info
- @pre_auth.scopes.each do |scope|
%li= t scope, scope: [:doorkeeper, :scopes]
%hr/
.actions
= form_tag oauth_authorization_path, method: :post do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= submit_tag "Authorize", class: "btn btn-success wide pull-left"
= form_tag oauth_authorization_path, method: :delete do
= hidden_field_tag :client_id, @pre_auth.client.uid
= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= submit_tag "Deny", class: "btn btn-danger prepend-left-10"
\ No newline at end of file
%h3.page-title Authorization code:
%main{:role => "main"}
%code#authorization_code= params[:code]
\ No newline at end of file
- submit_btn_css ||= 'btn btn-link btn-remove'
= form_tag oauth_authorized_application_path(application) do
%input{:name => "_method", :type => "hidden", :value => "delete"}/
= submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-small'
\ No newline at end of file
%header.page-header
%h1 Your authorized applications
%main{:role => "main"}
%table.table.table-striped
%thead
%tr
%th Application
%th Created At
%th
%th
%tbody
- @applications.each do |application|
%tr
%td= application.name
%td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
%td= render 'delete_form', application: application
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Doorkeeper</title>
<%= stylesheet_link_tag "doorkeeper/admin/application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<%= link_to 'OAuth2 Provider', oauth_applications_path, class: 'navbar-brand' %>
</div>
<ul class="nav navbar-nav">
<%= content_tag :li, class: "#{'active' if request.path == oauth_applications_path}" do %>
<%= link_to 'Applications', oauth_applications_path %>
<% end %>
</ul>
</div>
</div>
<div class="container">
<%- if flash[:notice].present? %>
<div class="alert alert-info">
<%= flash[:notice] %>
</div>
<% end -%>
<%= yield %>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>OAuth authorize required</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<%= stylesheet_link_tag "doorkeeper/application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id="container">
<%- if flash[:notice].present? %>
<div class="alert alert-info">
<%= flash[:notice] %>
</div>
<% end -%>
<%= yield %>
</div>
</body>
</html>
......@@ -3,7 +3,7 @@
= link_to profile_path, title: "Profile" do
%i.fa.fa-user
Profile
= nav_link(controller: :accounts) do
= nav_link(controller: [:accounts, :applications]) do
= link_to profile_account_path do
%i.fa.fa-gear
Account
......
......@@ -75,3 +75,38 @@
The following groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
= link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
%h3.page-title
OAuth2
%fieldset.oauth-applications
%legend Your applications
%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
%table.table.table-striped
%thead
%tr
%th Name
%th Callback URL
%th
%th
%tbody
- @applications.each do |application|
%tr{:id => "application_#{application.id}"}
%td= link_to application.name, oauth_application_path(application)
%td= application.redirect_uri
%td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-small'
%td= render 'doorkeeper/applications/delete_form', application: application
%fieldset.oauth-authorized-applications
%legend Your authorized applications
%table.table.table-striped
%thead
%tr
%th Name
%th Created At
%th
%tbody
- @authorized_applications.each do |application|
%tr{:id => "application_#{application.id}"}
%td= link_to application.name, oauth_application_path(application)
%td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
%td= render 'doorkeeper/authorized_applications/delete_form', application: application
Doorkeeper.configure do
# Change the ORM that doorkeeper will use.
# Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
orm :active_record
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
# Example implementation:
current_user || 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
# 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 => true
# Define access token scopes for your provider
# For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
default_scopes :api
#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_access_token_param, :from_bearer_authorization, :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 nil#'urn:ietf:wg:oauth:2.0:oob'
# 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 all the four grant flows.
#
# grant_flows %w(authorization_code implicit password 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 trusted a application.
# skip_authorization do |resource_owner, client|
# client.superapp? or resource_owner.admin?
# end
# WWW-Authenticate Realm (default "Doorkeeper").
# realm "Doorkeeper"
# Allow dynamic query parameters (disabled by default)
# Some applications require dynamic query parameters on their request_uri
# set to true if you want this to be allowed
# wildcard_redirect_uri false
end
en:
activerecord:
errors:
models:
application:
attributes:
redirect_uri:
fragment_present: 'cannot contain a fragment.'
invalid_uri: 'must be a valid URI.'
relative_uri: 'must be an absolute URI.'
mongoid:
errors:
models:
application:
attributes:
redirect_uri:
fragment_present: 'cannot contain a fragment.'
invalid_uri: 'must be a valid URI.'
relative_uri: 'must be an absolute URI.'
mongo_mapper:
errors:
models:
application:
attributes:
redirect_uri:
fragment_present: 'cannot contain a fragment.'
invalid_uri: 'must be a valid URI.'
relative_uri: 'must be an absolute URI.'
doorkeeper:
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"
scopes:
api: Access your API
flash:
applications:
create:
notice: 'Application created.'
destroy:
notice: 'Application deleted.'
update:
notice: 'Application updated.'
authorized_applications:
destroy:
notice: 'Application revoked.'
......@@ -2,6 +2,11 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
use_doorkeeper do
controllers :applications => 'oauth/applications',
:authorized_applications => 'oauth/authorized_applications',
:authorizations => 'oauth/authorizations'
end
#
# Search
#
......
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
end
add_index :oauth_applications, :uid, unique: true
create_table :oauth_access_grants do |t|
t.integer :resource_owner_id, null: false
t.integer :application_id, 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
create_table :oauth_access_tokens do |t|
t.integer :resource_owner_id
t.integer :application_id
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
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
end
end
class AddOwnerToApplication < ActiveRecord::Migration
def change
add_column :oauth_applications, :owner_id, :integer, null: true
add_column :oauth_applications, :owner_type, :string, null: true
add_index :oauth_applications, [:owner_id, :owner_type]
end
end
\ No newline at end of file
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20141205134006) do
ActiveRecord::Schema.define(version: 20141217125223) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -249,6 +249,49 @@ ActiveRecord::Schema.define(version: 20141205134006) do
add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
create_table "oauth_access_grants", force: true do |t|