Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Introduce application tokens #625

Merged
merged 1 commit into from
Dec 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ gem "redcarpet"
gem "font-awesome-rails"
gem "bootstrap-typeahead-rails"

# Used to store application tokens. This is already a Rails depedency. However
# better safe than sorry...
gem "bcrypt"

# This is already a Rails dependency, but we use it to run portusctl
gem "thor"

Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ DEPENDENCIES
active_record_union
awesome_print
base32
bcrypt
bootstrap-sass (~> 3.3.4)
bootstrap-typeahead-rails
byebug
Expand Down
12 changes: 12 additions & 0 deletions app/assets/javascripts/registrations.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
$(document).on "page:change", ->
$('#add_application_token_btn').on 'click', (event) ->
$('#add_application_token_form').toggle 400, "swing", ->
if $('#add_application_token_form').is(':visible')
$('#add_team_btn i').addClass("fa-minus-circle")
$('#add_team_btn i').removeClass("fa-plus-circle")
$('#team_name').focus()
layout_resizer()
else
$('#add_team_btn i').removeClass("fa-minus-circle")
$('#add_team_btn i').addClass("fa-plus-circle")
layout_resizer()
6 changes: 6 additions & 0 deletions app/assets/stylesheets/activities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
&.change-description {
background: $activity-change-description;
}
&.application-token-created {
background: $activity-application-token-created;
}
&.application-token-destroyed {
background: $activity-application-token-destroyed;
}
}
}
}
2 changes: 2 additions & 0 deletions app/assets/stylesheets/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ $activity-remove-member-team: $yellow;
$activity-change-role: $orange;
$activity-team-created: $pink;
$activity-repo-pushed: $purple;
$activity-application-token-created: $orange-dark;
$activity-application-token-destroyed: $red;


// placeholder
Expand Down
15 changes: 15 additions & 0 deletions app/controllers/api/v2/tokens_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
# use in order to perform operation into the registry. This is the last step in
# the authentication process for Portus' point of view.
class Api::V2::TokensController < Api::BaseController
before_action :attempt_authentication_against_application_tokens

# Try to perform authentication using the application tokens. The password
# provided via HTTP basic auth is going to be checked against the application
# tokens a user might have created.
# If the user has a valid application token then the other forms of
# authentication (Portus' database, LDAP) are going to be skipped.
def attempt_authentication_against_application_tokens
user = authenticate_with_http_basic do |username, password|
user = User.find_by(username: username)
user if user && user.application_token_valid?(password)
end
sign_in(user, store: true) if user
end

# Returns the token that the docker client should use in order to perform
# operation into the private registry.
def show
Expand Down
39 changes: 39 additions & 0 deletions app/controllers/application_tokens_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ApplicationTokensController manages the creation/removal of application tokens
class ApplicationTokensController < ApplicationController
respond_to :js

# POST /application_tokens
def create
@plain_token = Devise.friendly_token

@application_token = ApplicationToken.new(create_params)
@application_token.user = current_user
@application_token.token_salt = BCrypt::Engine.generate_salt
@application_token.token_hash = BCrypt::Engine.hash_secret(
@plain_token,
@application_token.token_salt)

if @application_token.save
@application_token.create_activity!(:create, current_user)
respond_with @application_token
else
respond_with @application_token.errors, status: :unprocessable_entity
end
end

# DELETE /application_token/1
def destroy
@application_token = ApplicationToken.find(params[:id])
@application_token.create_activity!(:destroy, current_user)
@application_token.destroy

respond_with @application_token
end

private

def create_params
permitted = [:application]
params.require(:application_token).permit(permitted)
end
end
25 changes: 25 additions & 0 deletions app/models/application_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ApplicationToken < ActiveRecord::Base
include PublicActivity::Common

belongs_to :user

validates :application, uniqueness: { scope: "user_id" }

validate :limit_number_of_tokens_per_user, on: :create

def limit_number_of_tokens_per_user
max_reached = ApplicationToken.where(user_id: user_id).count >= User::APPLICATION_TOKENS_MAX
errors.add(
:base,
"Users cannot have more than #{User::APPLICATION_TOKENS_MAX} " \
"application tokens") if max_reached
end

# Create the activity regarding this application token.
def create_activity!(type, owner)
create_activity(
type,
owner: owner,
parameters: { application: application })
end
end
16 changes: 16 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class User < ActiveRecord::Base
USERNAME_CHARS = "a-z0-9"
USERNAME_FORMAT = /\A[#{USERNAME_CHARS}]{4,30}\Z/

APPLICATION_TOKENS_MAX = 5
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not in the ApplicationToken model ? It looks like it would be more suitable there no ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added there because it's a setting that applies on a per user basis


validates :username, presence: true, uniqueness: true,
format: {
with: USERNAME_FORMAT,
Expand All @@ -18,6 +20,7 @@ class User < ActiveRecord::Base
has_many :team_users
has_many :teams, through: :team_users
has_many :stars
has_many :application_tokens

scope :not_portus, -> { where.not username: "portus" }
scope :enabled, -> { not_portus.where enabled: true }
Expand Down Expand Up @@ -112,6 +115,19 @@ def self.search_from_query(members, query)
enabled.where.not(id: members).where(arel_table[:username].matches(query))
end

# Looks for an application token that matches with the plain one provided
# as parameter.
# Return true if there's an application token matching it, false otherwise
def application_token_valid?(plain_token)
application_tokens.each do |t|
if t.token_hash == BCrypt::Engine.hash_secret(plain_token, t.token_salt)
return true
end
end

false
end

protected

# Returns whether the given user can be disabled or not. The following rules
Expand Down
11 changes: 10 additions & 1 deletion app/policies/public_activity/activity_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,16 @@ def resolve
"(team_users.user_id = ? OR namespaces.public = ?)",
"Repository", user.id, true)

team_activities.union_all(namespace_activities).union_all(repository_activities).distinct
# Show application tokens activities related only to the current user
application_token_activities = @scope
.where("activities.trackable_type = ? AND activities.owner_id = ?",
"ApplicationToken", user.id)

team_activities
.union_all(namespace_activities)
.union_all(repository_activities)
.union_all(application_token_activities)
.distinct
end
end
end
13 changes: 13 additions & 0 deletions app/views/application_tokens/_application_token.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
tr id="application_token_#{application_token.id}"
td= application_token.application
td
a[class="btn btn-default"
data-placement="left"
data-toggle="popover"
data-title="Please confirm"
data-content='<p>Are you sure you want to remove this token?</p><a class="btn btn-default">No</a> <a class="btn btn-primary" data-method="delete" rel="nofollow" data-remote="true" href="#{url_for(application_token)}">Yes</a>'
data-html="true"
tabindex="0"
role="button"
]
i.fa.fa-trash.fa-lg
13 changes: 13 additions & 0 deletions app/views/application_tokens/create.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<% if @application_token.errors.any? %>
$('#alert p').html("<%= escape_javascript(@application_token.errors.full_messages.join('<br/>')) %>");
$('#alert').fadeIn();
<% else %>
$("<%= escape_javascript(render @application_token) %>").appendTo("#application_tokens");
$('#alert p').html("New token created: <code><%= @plain_token %></code>");
$('#alert').fadeIn();
$('#add_application_token_form').fadeOut();
<% if current_user.application_tokens.count >= User::APPLICATION_TOKENS_MAX %>
$('#add_application_token_btn').attr("disabled", "disabled");
<% end %>
<% end %>

11 changes: 11 additions & 0 deletions app/views/application_tokens/destroy.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<% if @application_token.errors.any? %>
$('#alert p').html("<%= escape_javascript(@application_token.errors.full_messages.join('<br/>')) %>");
$('#alert').fadeIn();
<% else %>
$("#application_token_<%= @application_token.id %>").remove();
$('#alert p').html("<em>\"<%= @application_token.application %>\"</em> token has been removed");
$('#alert').fadeIn();
<% if current_user.application_tokens.count < User::APPLICATION_TOKENS_MAX %>
$('#add_application_token_btn').removeAttr("disabled");
<% end %>
<% end %>
35 changes: 35 additions & 0 deletions app/views/devise/registrations/edit.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,41 @@
.actions
= f.submit('Update', class: 'btn btn-primary', disabled: true)

#add_application_token_form.collapse
= form_for :application_token, url: application_tokens_path, remote: true, html: {id: 'new-application-token-form', class: 'form-horizontal', role: 'form'} do |f|
.form-group
= f.label :application, {class: 'control-label col-md-2'}
.col-md-7
= f.text_field(:application, class: 'form-control', required: true, placeholder: "Application's name")
.form-group
.col-md-offset-2.col-md-7
= f.submit('Create', class: 'btn btn-primary')

.panel.panel-default
.panel-heading
h5
' Application tokens
.pull-right
a#add_application_token_btn.btn.btn-xs.btn-link.js-toggle-button[
disabled="#{current_user.application_tokens.count >= User::APPLICATION_TOKENS_MAX ? 'disabled' : ''}"
role="button"
]
i.fa.fa-plus-circle
| Create new token
.panel-body
.table-responsive
table.table.table-striped.table-hover
colgroup
col.col-90
col.col-10
thead
tr
th Application
th Remove
tbody#application_tokens
- current_user.application_tokens.each do |token|
= render partial: 'application_tokens/application_token', locals: {application_token: token}

- if current_user.email?
- unless current_user.admin? && @admin_count == 1
.panel.panel-default
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= CSV.generate_line(["application token", "#{activity.parameters[:application]}", "create", "-", activity.owner.username, activity.created_at, "-"])
15 changes: 15 additions & 0 deletions app/views/public_activity/application_token/_create.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
li
.activity-type.application-token-created
i.fa.fa-key
.user-image
= user_image_tag(activity.owner.email)
.description
h6
strong
= activity.owner.username
| created a token for the "
em= activity.parameters[:application]
| " application
small
i.fa.fa-clock-o
= activity_time_tag activity.created_at
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= CSV.generate_line(["application token", "#{activity.parameters[:application]}", "destroy", "-", activity.owner.username, activity.created_at, "-"])
15 changes: 15 additions & 0 deletions app/views/public_activity/application_token/_destroy.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
li
.activity-type.application-token-destroyed
i.fa.fa-key
.user-image
= user_image_tag(activity.owner.email)
.description
h6
strong
= activity.owner.username
| removed the token of "
em= activity.parameters[:application]
| " application
small
i.fa.fa-clock-o
= activity_time_tag activity.created_at
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
resources :comments, only: [:create, :destroy]
end

resources :application_tokens, only: [:create, :destroy]

devise_for :users, controllers: { registrations: "auth/registrations",
sessions: "auth/sessions",
passwords: "passwords" }
Expand Down
12 changes: 12 additions & 0 deletions db/migrate/20151117181723_create_application_tokens.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class CreateApplicationTokens < ActiveRecord::Migration
def change
create_table :application_tokens do |t|
t.string :application, null: false
t.string :token_hash, null:false
t.string :token_salt, null:false
t.integer :user_id, null:false
end

add_index :application_tokens, :user_id
end
end
12 changes: 11 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20151124150353) do
ActiveRecord::Schema.define(version: 20151207153613) do

create_table "activities", force: :cascade do |t|
t.integer "trackable_id", limit: 4
Expand All @@ -31,6 +31,15 @@
add_index "activities", ["recipient_id", "recipient_type"], name: "index_activities_on_recipient_id_and_recipient_type", using: :btree
add_index "activities", ["trackable_id", "trackable_type"], name: "index_activities_on_trackable_id_and_trackable_type", using: :btree

create_table "application_tokens", force: :cascade do |t|
t.string "application", limit: 255, null: false
t.string "token_hash", limit: 255, null: false
t.string "token_salt", limit: 255, null: false
t.integer "user_id", limit: 4, null: false
end

add_index "application_tokens", ["user_id"], name: "index_application_tokens_on_user_id", using: :btree

create_table "comments", force: :cascade do |t|
t.text "body", limit: 65535
t.integer "repository_id", limit: 4
Expand Down Expand Up @@ -85,6 +94,7 @@
t.integer "namespace_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "source_url", limit: 255, default: "", null: false
end

add_index "repositories", ["name", "namespace_id"], name: "index_repositories_on_name_and_namespace_id", unique: true, using: :btree
Expand Down
Loading