redirect_to :action => 'account'
end
+ # Create a new API key
+ def reset_api_key
+ if request.post?
+ if User.current.api_token
+ User.current.api_token.destroy
+ User.current.reload
+ end
+ User.current.api_key
+ flash[:notice] = l(:notice_api_access_key_reseted)
+ end
+ redirect_to :action => 'account'
+ end
+
# User's page layout configuration
def page_layout
@user = User.current
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
+ has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source
# Active non-anonymous users scope
token = self.rss_token || Token.create(:user => self, :action => 'feeds')
token.value
end
+
+ # Return user's API key (a 40 chars long string), used to access the API
+ def api_key
+ token = self.api_token || Token.create(:user => self, :action => 'api')
+ token.value
+ end
# Return an array of project ids for which the user has explicitly turned mail notifications on
def notified_projects_ids
token && token.user.active? ? token.user : nil
end
+ def self.find_by_api_key(key)
+ token = Token.find_by_action_and_value('api', key)
+ token && token.user.active? ? token.user : nil
+ end
+
# Makes find_by_mail case-insensitive
def self.find_by_mail(mail)
find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
<p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br />
<%=l(:field_created_on)%>: <%= format_time(@user.created_on) %></p>
+
+
+<h4><%= l(:label_feeds_access_key) %></h4>
+
+<p>
<% if @user.rss_token %>
-<p><%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
-(<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)</p>
+<%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %>
+<% else %>
+<%= l(:label_missing_feeds_access_key) %>
+<% end %>
+(<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)
+</p>
+
+<h4><%= l(:label_api_access_key) %></h4>
+<p>
+<% if @user.api_token %>
+<%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
+<% else %>
+<%= l(:label_missing_api_access_key) %>
<% end %>
+(<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>)
+</p>
<p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
<% end %>
</div>
+
+<% if @user.api_token %>
+<h3><%=l(:label_api_access_key) %></h3>
+<div class="box">
+ <p>
+ <%= link_to_function(l(:text_show), "$('api-access-key').show();")%>
+ <pre id='api-access-key'><%= @user.api_key %></pre>
+ </p>
+ <%= javascript_tag("$('api-access-key').hide();") %>
+</div>
+<% end %>
+
</div>
<% end %>
notice_email_sent: "An email was sent to {{value}}"
notice_email_error: "An error occurred while sending mail ({{value}})"
notice_feeds_access_key_reseted: Your RSS access key was reset.
+ notice_api_access_key_reseted: Your API access key was reset.
notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
notice_account_pending: "Your account was created and is now pending administrator approval."
label_language_based: Based on user's language
label_sort_by: "Sort by {{value}}"
label_send_test_email: Send a test email
+ label_feeds_access_key: RSS access key
+ label_missing_feeds_access_key: Missing a RSS access key
label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
label_module_plural: Modules
label_added_time_by: "Added by {{author}} {{age}} ago"
label_copy_target: Target
label_copy_same_as_target: Same as target
label_display_used_statuses_only: Only display statuses that are used by this tracker
+ label_api_access_key: API access key
+ label_missing_api_access_key: Missing an API access key
+ label_api_access_key_created_on: "API access key created {{value}} ago"
button_login: Login
button_submit: Submit
text_wiki_page_nullify_children: "Keep child pages as root pages"
text_wiki_page_destroy_children: "Delete child pages and all their descendants"
text_wiki_page_reassign_children: "Reassign child pages to this parent page"
+ text_show: Show
default_role_manager: Manager
default_role_developper: Developer
--- /dev/null
+class AddApiKeysForUsers < ActiveRecord::Migration
+ def self.up
+ say_with_time("Generating API keys for active users") do
+ User.active.all(:include => :api_token).each do |user|
+ user.api_key
+ end
+ end
+ end
+
+ def self.down
+ # No-op
+ end
+end
should_redirect_to('my account') {'/my/account' }
end
end
+
+ context "POST to reset_api_key" do
+ context "with an existing api_token" do
+ setup do
+ @previous_token_value = User.find(2).api_key # Will generate one if it's missing
+ post :reset_api_key
+ end
+
+ should "destroy the existing token" do
+ assert_not_equal @previous_token_value, User.find(2).api_key
+ end
+
+ should "create a new token" do
+ assert User.find(2).api_token
+ end
+
+ should_set_the_flash_to /reset/
+ should_redirect_to('my account') {'/my/account' }
+ end
+
+ context "with no api_token" do
+ setup do
+ assert_nil User.find(2).api_token
+ post :reset_api_key
+ end
+
+ should "create a new token" do
+ assert User.find(2).api_token
+ end
+
+ should_set_the_flash_to /reset/
+ should_redirect_to('my account') {'/my/account' }
+ end
+ end
end
assert !anon.new_record?
assert_kind_of AnonymousUser, anon
end
-
+
+ should_have_one :rss_token
+
def test_rss_key
assert_nil @jsmith.rss_token
key = @jsmith.rss_key
@jsmith.reload
assert_equal key, @jsmith.rss_key
end
+
+ should_have_one :api_token
+
+ context "User#api_key" do
+ should "generate a new one if the user doesn't have one" do
+ user = User.generate_with_protected!(:api_token => nil)
+ assert_nil user.api_token
+
+ key = user.api_key
+ assert_equal 40, key.length
+ user.reload
+ assert_equal key, user.api_key
+ end
+
+ should "return the existing api token value" do
+ user = User.generate_with_protected!
+ token = Token.generate!(:action => 'api')
+ user.api_token = token
+ assert user.save
+
+ assert_equal token.value, user.api_key
+ end
+ end
+
+ context "User#find_by_api_key" do
+ should "return nil if no matching key is found" do
+ assert_nil User.find_by_api_key('zzzzzzzzz')
+ end
+
+ should "return nil if the key is found for an inactive user" do
+ user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
+ token = Token.generate!(:action => 'api')
+ user.api_token = token
+ user.save
+
+ assert_nil User.find_by_api_key(token.value)
+ end
+
+ should "return the user if the key is found for an active user" do
+ user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
+ token = Token.generate!(:action => 'api')
+ user.api_token = token
+ user.save
+
+ assert_equal user, User.find_by_api_key(token.value)
+ end
+ end
+
def test_roles_for_project
# user with a role
roles = @jsmith.roles_for_project(Project.find(1))