Feature #24808 » 0003-oauth-Add-OAuth2-provider-capability-using-doorkeepe.patch
| Gemfile | ||
|---|---|---|
| 18 | 18 | 
    gem "rbpdf", "~> 1.20.0"  | 
| 19 | 19 | 
    gem 'addressable'  | 
| 20 | 20 | 
    gem 'rubyzip', (RUBY_VERSION < '2.4' ? '~> 1.3.0' : '~> 2.3.0')  | 
| 21 | 
    gem "doorkeeper", "~> 4.4.0"  | 
|
| 22 | 
    gem "doorkeeper-i18n", "~> 4.0"  | 
|
| 21 | 23 | |
| 22 | 24 | 
    # Windows does not include zoneinfo files, so bundle the tzinfo-data gem  | 
| 23 | 25 | 
    gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin]  | 
| app/controllers/application_controller.rb | ||
|---|---|---|
| 123 | 123 | 
    if (key = api_key_from_request)  | 
| 124 | 124 | 
    # Use API key  | 
| 125 | 125 | 
    user = User.find_by_api_key(key)  | 
| 126 | 
    elsif access_token = Doorkeeper.authenticate(request)  | 
|
| 127 | 
    # Oauth  | 
|
| 128 | 
    if access_token.accessible?  | 
|
| 129 | 
    user = User.active.find_by_id(access_token.resource_owner_id)  | 
|
| 130 | 
    else  | 
|
| 131 | 
    doorkeeper_render_error  | 
|
| 132 | 
    end  | 
|
| 126 | 133 | 
    elsif /\ABasic /i.match?(request.authorization.to_s)  | 
| 127 | 134 | 
    # HTTP Basic, either username/password or API key/random  | 
| 128 | 135 | 
    authenticate_with_http_basic do |username, password|  | 
| app/views/my/account.html.erb | ||
|---|---|---|
| 1 | 1 | 
    <div class="contextual">  | 
| 2 | 2 | 
    <%= additional_emails_link(@user) %>  | 
| 3 | 3 | 
    <%= link_to(l(:button_change_password), {:action => 'password'}, :class => 'icon icon-passwd') if @user.change_password_allowed? %>
   | 
| 4 | 
    <%= link_to(t('doorkeeper.applications.index.title'), oauth_authorized_applications_path, :class => 'icon icon-applications') if Setting.rest_api_enabled? %>
   | 
|
| 4 | 5 | 
    <%= call_hook(:view_my_account_contextual, :user => @user)%>  | 
| 5 | 6 | 
    </div>  | 
| 6 | 7 | |
| config/initializers/doorkeeper.rb | ||
|---|---|---|
| 1 | 
    Doorkeeper.configure do  | 
|
| 2 | 
    use_refresh_token  | 
|
| 3 | 
    reuse_access_token  | 
|
| 4 | 
    realm Redmine::Info.app_name  | 
|
| 5 | 
    default_scopes :public  | 
|
| 6 | ||
| 7 | 
    resource_owner_authenticator do  | 
|
| 8 | 
    if Setting.rest_api_enabled?  | 
|
| 9 | 
    User.active.find_by_id(session[:user_id]) || redirect_to(signin_path(:back_url => request.original_url))  | 
|
| 10 | 
    else  | 
|
| 11 | 
    render(:text => 'Forbidden', :status => 403)  | 
|
| 12 | 
    end  | 
|
| 13 | 
    end  | 
|
| 14 | ||
| 15 | 
    admin_authenticator do  | 
|
| 16 | 
    if !Setting.rest_api_enabled? || !User.active.where(admin: true).find_by_id(session[:user_id])  | 
|
| 17 | 
    render(:text => 'Forbidden', :status => 403)  | 
|
| 18 | 
    end  | 
|
| 19 | 
    end  | 
|
| 20 | ||
| 21 | 
    end  | 
|
| config/routes.rb | ||
|---|---|---|
| 18 | 18 | 
    # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  | 
| 19 | 19 | |
| 20 | 20 | 
    Rails.application.routes.draw do  | 
| 21 | ||
| 22 | 
    use_doorkeeper  | 
|
| 23 | ||
| 24 | 
    root :to => 'welcome#index'  | 
|
| 21 | 25 | 
    root :to => 'welcome#index', :as => 'home'  | 
| 22 | 26 | |
| 23 | 27 | 
    match 'login', :to => 'account#login', :as => 'signin', :via => [:get, :post]  | 
| db/migrate/20170107092155_create_doorkeeper_tables.rb | ||
|---|---|---|
| 1 | 
    class CreateDoorkeeperTables < ActiveRecord::Migration[4.2]  | 
|
| 2 | 
    def change  | 
|
| 3 | 
    create_table :oauth_applications do |t|  | 
|
| 4 | 
    t.string :name, null: false  | 
|
| 5 | 
    t.string :uid, null: false  | 
|
| 6 | 
    t.string :secret, null: false  | 
|
| 7 | 
    t.text :redirect_uri, null: false  | 
|
| 8 | 
    t.text :scopes, null: false  | 
|
| 9 | 
    t.boolean :confidential, null: false, default: true  | 
|
| 10 | 
    t.timestamps null: false  | 
|
| 11 | 
    end  | 
|
| 12 | ||
| 13 | 
    add_index :oauth_applications, :uid, unique: true  | 
|
| 14 | ||
| 15 | 
    create_table :oauth_access_grants do |t|  | 
|
| 16 | 
    t.integer :resource_owner_id, null: false  | 
|
| 17 | 
    t.references :application, null: false  | 
|
| 18 | 
    t.string :token, null: false  | 
|
| 19 | 
    t.integer :expires_in, null: false  | 
|
| 20 | 
    t.text :redirect_uri, null: false  | 
|
| 21 | 
    t.datetime :created_at, null: false  | 
|
| 22 | 
    t.datetime :revoked_at  | 
|
| 23 | 
    t.text :scopes  | 
|
| 24 | 
    end  | 
|
| 25 | ||
| 26 | 
    add_index :oauth_access_grants, :token, unique: true  | 
|
| 27 | 
    add_foreign_key(  | 
|
| 28 | 
    :oauth_access_grants,  | 
|
| 29 | 
    :oauth_applications,  | 
|
| 30 | 
    column: :application_id  | 
|
| 31 | 
    )  | 
|
| 32 | 
    add_foreign_key(  | 
|
| 33 | 
    :oauth_access_grants,  | 
|
| 34 | 
    :users,  | 
|
| 35 | 
    column: :resource_owner_id  | 
|
| 36 | 
    )  | 
|
| 37 | ||
| 38 | 
    create_table :oauth_access_tokens do |t|  | 
|
| 39 | 
    t.integer :resource_owner_id  | 
|
| 40 | 
    t.references :application  | 
|
| 41 | ||
| 42 | 
    t.string :token, null: false  | 
|
| 43 | ||
| 44 | 
    t.string :refresh_token  | 
|
| 45 | 
    t.integer :expires_in  | 
|
| 46 | 
    t.datetime :revoked_at  | 
|
| 47 | 
    t.datetime :created_at, null: false  | 
|
| 48 | 
    t.text :scopes  | 
|
| 49 | ||
| 50 | 
    t.string :previous_refresh_token, null: false, default: ""  | 
|
| 51 | 
    end  | 
|
| 52 | ||
| 53 | 
    add_index :oauth_access_tokens, :token, unique: true  | 
|
| 54 | 
    add_index :oauth_access_tokens, :resource_owner_id  | 
|
| 55 | 
    add_index :oauth_access_tokens, :refresh_token, unique: true  | 
|
| 56 | ||
| 57 | 
    add_foreign_key(  | 
|
| 58 | 
    :oauth_access_tokens,  | 
|
| 59 | 
    :oauth_applications,  | 
|
| 60 | 
    column: :application_id  | 
|
| 61 | 
    )  | 
|
| 62 | 
    add_foreign_key(  | 
|
| 63 | 
    :oauth_access_tokens,  | 
|
| 64 | 
    :users,  | 
|
| 65 | 
    column: :resource_owner_id  | 
|
| 66 | 
    )  | 
|
| 67 | 
    end  | 
|
| 68 | 
    end  | 
|
| lib/redmine.rb | ||
|---|---|---|
| 267 | 267 | 
                :html => {:class => 'icon icon-settings'}
   | 
| 268 | 268 | 
      menu.push :ldap_authentication, {:controller => 'auth_sources', :action => 'index'},
   | 
| 269 | 269 | 
                :html => {:class => 'icon icon-server-authentication'}
   | 
| 270 | 
      menu.push :applications, {:controller => 'doorkeeper/applications', :action => 'index'},
   | 
|
| 271 | 
                :if => Proc.new { Setting.rest_api_enabled? },
   | 
|
| 272 | 
    :caption => :'doorkeeper.layouts.admin.nav.applications',  | 
|
| 273 | 
                :html => {:class => 'icon icon-applications'}
   | 
|
| 270 | 274 | 
      menu.push :plugins, {:controller => 'admin', :action => 'plugins'}, :last => true,
   | 
| 271 | 275 | 
                :html => {:class => 'icon icon-plugins'}
   | 
| 272 | 276 | 
      menu.push :info, {:controller => 'admin', :action => 'info'}, :caption => :label_information_plural, :last => true,
   | 
| public/stylesheets/application.css | ||
|---|---|---|
| 1544 | 1544 | 
    .icon-workflows { background-image: url(../images/ticket_go.png); }
   | 
| 1545 | 1545 | 
    .icon-custom-fields { background-image: url(../images/textfield.png); }
   | 
| 1546 | 1546 | 
    .icon-plugins { background-image: url(../images/plugin.png); }
   | 
| 1547 | 
    .icon-applications { background-image: url(../images/application_view_tile.png); }
   | 
|
| 1547 | 1548 | 
    .icon-news { background-image: url(../images/news.png); }
   | 
| 1548 | 1549 | 
    .icon-issue-closed { background-image: url(../images/ticket_checked.png); }
   | 
| 1549 | 1550 | 
    .icon-issue-note { background-image: url(../images/ticket_note.png); }
   | 
| test/unit/lib/redmine/i18n_test.rb | ||
|---|---|---|
| 185 | 185 | 
    def test_languages_options  | 
| 186 | 186 | 
    options = languages_options  | 
| 187 | 187 | 
    assert options.is_a?(Array)  | 
| 188 | 
    assert_equal valid_languages.size, options.size  | 
|
| 188 | 
        assert_equal valid_languages.select {|locale| ::I18n.exists?(:general_lang_name, locale)}.size, options.size
   | 
|
| 189 | 189 | 
        assert_nil options.detect {|option| !option.is_a?(Array)}
   | 
| 190 | 190 | 
        assert_nil options.detect {|option| option.size != 2}
   | 
| 191 | 191 | 
        assert_nil options.detect {|option| !option.first.is_a?(String) || !option.last.is_a?(String)}
   | 
| ... | ... | |
| 205 | 205 | |
| 206 | 206 | 
    def test_locales_validness  | 
| 207 | 207 | 
        lang_files_count = Dir["#{Rails.root}/config/locales/*.yml"].size
   | 
| 208 | 
    assert_equal lang_files_count, valid_languages.size  | 
|
| 208 | 
        assert_equal lang_files_count, valid_languages.select {|locale| ::I18n.exists?(:general_lang_name, locale)}.size
   | 
|
| 209 | 209 | 
    valid_languages.each do |lang|  | 
| 210 | 210 | 
    assert set_language_if_valid(lang)  | 
| 211 | 211 | 
    end  |