@@ -0,0 +1,13 | |||||
|
1 | class AddApiKeysForUsers < ActiveRecord::Migration | |||
|
2 | def self.up | |||
|
3 | say_with_time("Generating API keys for active users") do | |||
|
4 | User.active.all(:include => :api_token).each do |user| | |||
|
5 | user.api_key | |||
|
6 | end | |||
|
7 | end | |||
|
8 | end | |||
|
9 | ||||
|
10 | def self.down | |||
|
11 | # No-op | |||
|
12 | end | |||
|
13 | end |
@@ -108,6 +108,19 class MyController < ApplicationController | |||||
108 | redirect_to :action => 'account' |
|
108 | redirect_to :action => 'account' | |
109 | end |
|
109 | end | |
110 |
|
110 | |||
|
111 | # Create a new API key | |||
|
112 | def reset_api_key | |||
|
113 | if request.post? | |||
|
114 | if User.current.api_token | |||
|
115 | User.current.api_token.destroy | |||
|
116 | User.current.reload | |||
|
117 | end | |||
|
118 | User.current.api_key | |||
|
119 | flash[:notice] = l(:notice_api_access_key_reseted) | |||
|
120 | end | |||
|
121 | redirect_to :action => 'account' | |||
|
122 | end | |||
|
123 | ||||
111 | # User's page layout configuration |
|
124 | # User's page layout configuration | |
112 | def page_layout |
|
125 | def page_layout | |
113 | @user = User.current |
|
126 | @user = User.current |
@@ -39,6 +39,7 class User < Principal | |||||
39 | has_many :changesets, :dependent => :nullify |
|
39 | has_many :changesets, :dependent => :nullify | |
40 | has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' |
|
40 | has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' | |
41 | has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'" |
|
41 | has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'" | |
|
42 | has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'" | |||
42 | belongs_to :auth_source |
|
43 | belongs_to :auth_source | |
43 |
|
44 | |||
44 | # Active non-anonymous users scope |
|
45 | # Active non-anonymous users scope | |
@@ -192,6 +193,12 class User < Principal | |||||
192 | token = self.rss_token || Token.create(:user => self, :action => 'feeds') |
|
193 | token = self.rss_token || Token.create(:user => self, :action => 'feeds') | |
193 | token.value |
|
194 | token.value | |
194 | end |
|
195 | end | |
|
196 | ||||
|
197 | # Return user's API key (a 40 chars long string), used to access the API | |||
|
198 | def api_key | |||
|
199 | token = self.api_token || Token.create(:user => self, :action => 'api') | |||
|
200 | token.value | |||
|
201 | end | |||
195 |
|
202 | |||
196 | # Return an array of project ids for which the user has explicitly turned mail notifications on |
|
203 | # Return an array of project ids for which the user has explicitly turned mail notifications on | |
197 | def notified_projects_ids |
|
204 | def notified_projects_ids | |
@@ -210,6 +217,11 class User < Principal | |||||
210 | token && token.user.active? ? token.user : nil |
|
217 | token && token.user.active? ? token.user : nil | |
211 | end |
|
218 | end | |
212 |
|
219 | |||
|
220 | def self.find_by_api_key(key) | |||
|
221 | token = Token.find_by_action_and_value('api', key) | |||
|
222 | token && token.user.active? ? token.user : nil | |||
|
223 | end | |||
|
224 | ||||
213 | # Makes find_by_mail case-insensitive |
|
225 | # Makes find_by_mail case-insensitive | |
214 | def self.find_by_mail(mail) |
|
226 | def self.find_by_mail(mail) | |
215 | find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase]) |
|
227 | find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase]) |
@@ -2,7 +2,25 | |||||
2 |
|
2 | |||
3 | <p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br /> |
|
3 | <p><%=l(:field_login)%>: <strong><%= @user.login %></strong><br /> | |
4 | <%=l(:field_created_on)%>: <%= format_time(@user.created_on) %></p> |
|
4 | <%=l(:field_created_on)%>: <%= format_time(@user.created_on) %></p> | |
|
5 | ||||
|
6 | ||||
|
7 | <h4><%= l(:label_feeds_access_key) %></h4> | |||
|
8 | ||||
|
9 | <p> | |||
5 | <% if @user.rss_token %> |
|
10 | <% if @user.rss_token %> | |
6 |
|
|
11 | <%= l(:label_feeds_access_key_created_on, distance_of_time_in_words(Time.now, @user.rss_token.created_on)) %> | |
7 | (<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>)</p> |
|
12 | <% else %> | |
|
13 | <%= l(:label_missing_feeds_access_key) %> | |||
|
14 | <% end %> | |||
|
15 | (<%= link_to l(:button_reset), {:action => 'reset_rss_key'}, :method => :post %>) | |||
|
16 | </p> | |||
|
17 | ||||
|
18 | <h4><%= l(:label_api_access_key) %></h4> | |||
|
19 | <p> | |||
|
20 | <% if @user.api_token %> | |||
|
21 | <%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %> | |||
|
22 | <% else %> | |||
|
23 | <%= l(:label_missing_api_access_key) %> | |||
8 | <% end %> |
|
24 | <% end %> | |
|
25 | (<%= link_to l(:button_reset), {:action => 'reset_api_key'}, :method => :post %>) | |||
|
26 | </p> |
@@ -51,6 +51,18 | |||||
51 | <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p> |
|
51 | <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p> | |
52 | <% end %> |
|
52 | <% end %> | |
53 | </div> |
|
53 | </div> | |
|
54 | ||||
|
55 | <% if @user.api_token %> | |||
|
56 | <h3><%=l(:label_api_access_key) %></h3> | |||
|
57 | <div class="box"> | |||
|
58 | <p> | |||
|
59 | <%= link_to_function(l(:text_show), "$('api-access-key').show();")%> | |||
|
60 | <pre id='api-access-key'><%= @user.api_key %></pre> | |||
|
61 | </p> | |||
|
62 | <%= javascript_tag("$('api-access-key').hide();") %> | |||
|
63 | </div> | |||
|
64 | <% end %> | |||
|
65 | ||||
54 | </div> |
|
66 | </div> | |
55 | <% end %> |
|
67 | <% end %> | |
56 |
|
68 |
@@ -142,6 +142,7 en: | |||||
142 | notice_email_sent: "An email was sent to {{value}}" |
|
142 | notice_email_sent: "An email was sent to {{value}}" | |
143 | notice_email_error: "An error occurred while sending mail ({{value}})" |
|
143 | notice_email_error: "An error occurred while sending mail ({{value}})" | |
144 | notice_feeds_access_key_reseted: Your RSS access key was reset. |
|
144 | notice_feeds_access_key_reseted: Your RSS access key was reset. | |
|
145 | notice_api_access_key_reseted: Your API access key was reset. | |||
145 | notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}." |
|
146 | notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}." | |
146 | notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." |
|
147 | notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." | |
147 | notice_account_pending: "Your account was created and is now pending administrator approval." |
|
148 | notice_account_pending: "Your account was created and is now pending administrator approval." | |
@@ -668,6 +669,8 en: | |||||
668 | label_language_based: Based on user's language |
|
669 | label_language_based: Based on user's language | |
669 | label_sort_by: "Sort by {{value}}" |
|
670 | label_sort_by: "Sort by {{value}}" | |
670 | label_send_test_email: Send a test email |
|
671 | label_send_test_email: Send a test email | |
|
672 | label_feeds_access_key: RSS access key | |||
|
673 | label_missing_feeds_access_key: Missing a RSS access key | |||
671 | label_feeds_access_key_created_on: "RSS access key created {{value}} ago" |
|
674 | label_feeds_access_key_created_on: "RSS access key created {{value}} ago" | |
672 | label_module_plural: Modules |
|
675 | label_module_plural: Modules | |
673 | label_added_time_by: "Added by {{author}} {{age}} ago" |
|
676 | label_added_time_by: "Added by {{author}} {{age}} ago" | |
@@ -729,6 +732,9 en: | |||||
729 | label_copy_target: Target |
|
732 | label_copy_target: Target | |
730 | label_copy_same_as_target: Same as target |
|
733 | label_copy_same_as_target: Same as target | |
731 | label_display_used_statuses_only: Only display statuses that are used by this tracker |
|
734 | label_display_used_statuses_only: Only display statuses that are used by this tracker | |
|
735 | label_api_access_key: API access key | |||
|
736 | label_missing_api_access_key: Missing an API access key | |||
|
737 | label_api_access_key_created_on: "API access key created {{value}} ago" | |||
732 |
|
738 | |||
733 | button_login: Login |
|
739 | button_login: Login | |
734 | button_submit: Submit |
|
740 | button_submit: Submit | |
@@ -836,6 +842,7 en: | |||||
836 | text_wiki_page_nullify_children: "Keep child pages as root pages" |
|
842 | text_wiki_page_nullify_children: "Keep child pages as root pages" | |
837 | text_wiki_page_destroy_children: "Delete child pages and all their descendants" |
|
843 | text_wiki_page_destroy_children: "Delete child pages and all their descendants" | |
838 | text_wiki_page_reassign_children: "Reassign child pages to this parent page" |
|
844 | text_wiki_page_reassign_children: "Reassign child pages to this parent page" | |
|
845 | text_show: Show | |||
839 |
|
846 | |||
840 | default_role_manager: Manager |
|
847 | default_role_manager: Manager | |
841 | default_role_developper: Developer |
|
848 | default_role_developper: Developer |
@@ -163,4 +163,38 class MyControllerTest < ActionController::TestCase | |||||
163 | should_redirect_to('my account') {'/my/account' } |
|
163 | should_redirect_to('my account') {'/my/account' } | |
164 | end |
|
164 | end | |
165 | end |
|
165 | end | |
|
166 | ||||
|
167 | context "POST to reset_api_key" do | |||
|
168 | context "with an existing api_token" do | |||
|
169 | setup do | |||
|
170 | @previous_token_value = User.find(2).api_key # Will generate one if it's missing | |||
|
171 | post :reset_api_key | |||
|
172 | end | |||
|
173 | ||||
|
174 | should "destroy the existing token" do | |||
|
175 | assert_not_equal @previous_token_value, User.find(2).api_key | |||
|
176 | end | |||
|
177 | ||||
|
178 | should "create a new token" do | |||
|
179 | assert User.find(2).api_token | |||
|
180 | end | |||
|
181 | ||||
|
182 | should_set_the_flash_to /reset/ | |||
|
183 | should_redirect_to('my account') {'/my/account' } | |||
|
184 | end | |||
|
185 | ||||
|
186 | context "with no api_token" do | |||
|
187 | setup do | |||
|
188 | assert_nil User.find(2).api_token | |||
|
189 | post :reset_api_key | |||
|
190 | end | |||
|
191 | ||||
|
192 | should "create a new token" do | |||
|
193 | assert User.find(2).api_token | |||
|
194 | end | |||
|
195 | ||||
|
196 | should_set_the_flash_to /reset/ | |||
|
197 | should_redirect_to('my account') {'/my/account' } | |||
|
198 | end | |||
|
199 | end | |||
166 | end |
|
200 | end |
@@ -126,7 +126,9 class UserTest < ActiveSupport::TestCase | |||||
126 | assert !anon.new_record? |
|
126 | assert !anon.new_record? | |
127 | assert_kind_of AnonymousUser, anon |
|
127 | assert_kind_of AnonymousUser, anon | |
128 | end |
|
128 | end | |
129 |
|
129 | |||
|
130 | should_have_one :rss_token | |||
|
131 | ||||
130 | def test_rss_key |
|
132 | def test_rss_key | |
131 | assert_nil @jsmith.rss_token |
|
133 | assert_nil @jsmith.rss_token | |
132 | key = @jsmith.rss_key |
|
134 | key = @jsmith.rss_key | |
@@ -135,7 +137,55 class UserTest < ActiveSupport::TestCase | |||||
135 | @jsmith.reload |
|
137 | @jsmith.reload | |
136 | assert_equal key, @jsmith.rss_key |
|
138 | assert_equal key, @jsmith.rss_key | |
137 | end |
|
139 | end | |
|
140 | ||||
138 |
|
141 | |||
|
142 | should_have_one :api_token | |||
|
143 | ||||
|
144 | context "User#api_key" do | |||
|
145 | should "generate a new one if the user doesn't have one" do | |||
|
146 | user = User.generate_with_protected!(:api_token => nil) | |||
|
147 | assert_nil user.api_token | |||
|
148 | ||||
|
149 | key = user.api_key | |||
|
150 | assert_equal 40, key.length | |||
|
151 | user.reload | |||
|
152 | assert_equal key, user.api_key | |||
|
153 | end | |||
|
154 | ||||
|
155 | should "return the existing api token value" do | |||
|
156 | user = User.generate_with_protected! | |||
|
157 | token = Token.generate!(:action => 'api') | |||
|
158 | user.api_token = token | |||
|
159 | assert user.save | |||
|
160 | ||||
|
161 | assert_equal token.value, user.api_key | |||
|
162 | end | |||
|
163 | end | |||
|
164 | ||||
|
165 | context "User#find_by_api_key" do | |||
|
166 | should "return nil if no matching key is found" do | |||
|
167 | assert_nil User.find_by_api_key('zzzzzzzzz') | |||
|
168 | end | |||
|
169 | ||||
|
170 | should "return nil if the key is found for an inactive user" do | |||
|
171 | user = User.generate_with_protected!(:status => User::STATUS_LOCKED) | |||
|
172 | token = Token.generate!(:action => 'api') | |||
|
173 | user.api_token = token | |||
|
174 | user.save | |||
|
175 | ||||
|
176 | assert_nil User.find_by_api_key(token.value) | |||
|
177 | end | |||
|
178 | ||||
|
179 | should "return the user if the key is found for an active user" do | |||
|
180 | user = User.generate_with_protected!(:status => User::STATUS_ACTIVE) | |||
|
181 | token = Token.generate!(:action => 'api') | |||
|
182 | user.api_token = token | |||
|
183 | user.save | |||
|
184 | ||||
|
185 | assert_equal user, User.find_by_api_key(token.value) | |||
|
186 | end | |||
|
187 | end | |||
|
188 | ||||
139 | def test_roles_for_project |
|
189 | def test_roles_for_project | |
140 | # user with a role |
|
190 | # user with a role | |
141 | roles = @jsmith.roles_for_project(Project.find(1)) |
|
191 | roles = @jsmith.roles_for_project(Project.find(1)) |
General Comments 0
You need to be logged in to leave comments.
Login now