diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6 index 932454aa07c..ff0ae124de1 100644 --- a/app/assets/javascripts/discourse/models/user.js.es6 +++ b/app/assets/javascripts/discourse/models/user.js.es6 @@ -73,6 +73,46 @@ const User = RestModel.extend({ return Discourse.getURL(`/users/${this.get('username_lower')}`); }, + @computed() + userApiKeys() { + const keys = this.get('user_api_keys'); + if (keys) { + return keys.map((raw)=>{ + let obj = Em.Object.create( + raw + ); + + obj.revoke = () => { + this.revokeApiKey(obj); + }; + + obj.undoRevoke = () => { + this.undoRevokeApiKey(obj); + }; + + return obj; + }); + } + }, + + revokeApiKey(key) { + return ajax("/user-api-key/revoke", { + type: 'POST', + data: { id: key.get('id') } + }).then(()=>{ + key.set('revoked', true); + }); + }, + + undoRevokeApiKey(key){ + return ajax("/user-api-key/undo-revoke", { + type: 'POST', + data: { id: key.get('id') } + }).then(()=>{ + key.set('revoked', false); + }); + }, + pmPath(topic) { const userId = this.get('id'); const username = this.get('username_lower'); diff --git a/app/assets/javascripts/discourse/routes/user.js.es6 b/app/assets/javascripts/discourse/routes/user.js.es6 index fdfb99854da..03bfbb401b0 100644 --- a/app/assets/javascripts/discourse/routes/user.js.es6 +++ b/app/assets/javascripts/discourse/routes/user.js.es6 @@ -19,7 +19,15 @@ export default Discourse.Route.extend({ const isIndexStream = INDEX_STREAM_ROUTES.indexOf(transition.targetName) !== -1; this.controllerFor('user').set('indexStream', isIndexStream); return true; - } + }, + + undoRevokeApiKey(key) { + key.undoRevoke(); + }, + + revokeApiKey(key) { + key.revoke(); + }, }, beforeModel() { diff --git a/app/assets/javascripts/discourse/templates/user/preferences.hbs b/app/assets/javascripts/discourse/templates/user/preferences.hbs index 660f74c501a..928fbf900ca 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.hbs +++ b/app/assets/javascripts/discourse/templates/user/preferences.hbs @@ -319,6 +319,26 @@ {{/if}} + {{#if model.userApiKeys}} +
+ +
+ {{#each model.userApiKeys as |key|}} +
+ {{key.application_name}} + {{#if key.revoked}} + {{d-button action="undoRevokeApiKey" actionParam=key class="btn" label="user.undo_revoke_access"}} + {{else}} + {{d-button action="revokeApiKey" actionParam=key class="btn" label="user.revoke_access"}} + {{/if}} +

{{i18n "user.api_permissions"}} {{#if key.write}}{{i18n "user.api_read_write"}}{{else}}{{i18n "user.api_read"}}{{/if}}

+

{{i18n "user.api_approved"}} {{bound-date key.created_at}}

+
+ {{/each}} +
+
+ {{/if}} + {{plugin-outlet "user-custom-controls"}}
diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index cfa80de4651..59140a3ced7 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -4,7 +4,7 @@ class UserApiKeysController < ApplicationController skip_before_filter :redirect_to_login_if_required, only: [:new] skip_before_filter :check_xhr, :preload_json - before_filter :ensure_logged_in, only: [:create] + before_filter :ensure_logged_in, only: [:create, :revoke, :undo_revoke] def new require_params @@ -47,6 +47,9 @@ class UserApiKeysController < ApplicationController validate_params + # destroy any old keys we had + UserApiKey.where(user_id: current_user.id, client_id: params[:client_id]).destroy_all + key = UserApiKey.create!( application_name: params[:application_name], client_id: params[:client_id], @@ -72,6 +75,22 @@ class UserApiKeysController < ApplicationController redirect_to "#{params[:auth_redirect]}?payload=#{CGI.escape(payload)}" end + def revoke + find_key.update_columns(revoked_at: Time.zone.now) + render json: success_json + end + + def undo_revoke + find_key.update_columns(revoked_at: nil) + render json: success_json + end + + def find_key + key = UserApiKey.find(params[:id]) + raise Discourse::InvalidAccess unless current_user.admin || key.user_id = current_user.id + key + end + def require_params [ :public_key, diff --git a/app/models/user.rb b/app/models/user.rb index 34b178cbcd3..ee5f4e3fa15 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,7 @@ class User < ActiveRecord::Base has_many :topic_users, dependent: :destroy has_many :category_users, dependent: :destroy has_many :tag_users, dependent: :destroy + has_many :user_api_keys, dependent: :destroy has_many :topics has_many :user_open_ids, dependent: :destroy has_many :user_actions, dependent: :destroy diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 673266f5818..48ddfa89ccf 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -102,7 +102,8 @@ class UserSerializer < BasicUserSerializer :card_image_badge_id, :muted_usernames, :mailing_list_posts_per_day, - :can_change_bio + :can_change_bio, + :user_api_keys untrusted_attributes :bio_raw, :bio_cooked, @@ -137,6 +138,21 @@ class UserSerializer < BasicUserSerializer !(SiteSetting.enable_sso && SiteSetting.sso_overrides_bio) end + + def user_api_keys + keys = object.user_api_keys.where(revoked_at: nil).map do |k| + { + id: k.id, + application_name: k.application_name, + read: k.read, + write: k.write, + created_at: k.created_at + } + end + + keys.length > 0 ? keys : nil + end + def card_badge object.user_profile.card_image_badge end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7a947239b0b..012a54c5d8f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -571,6 +571,13 @@ en: muted_topics_link: "Show muted topics" watched_topics_link: "Show watched topics" automatically_unpin_topics: "Automatically unpin topics when I reach the bottom." + apps: "Apps" + revoke_access: "Revoke Access" + undo_revoke_access: "Undo Revoke Access" + api_permissions: "Permissions:" + api_approved: "Approved:" + api_read: "read" + api_read_write: "read and write" staff_counters: flags_given: "helpful flags" diff --git a/config/routes.rb b/config/routes.rb index dd3b1e4a02c..30d47a8fad7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -664,6 +664,8 @@ Discourse::Application.routes.draw do get "/user-api-key/new" => "user_api_keys#new" post "/user-api-key" => "user_api_keys#create" + post "/user-api-key/revoke" => "user_api_keys#revoke" + post "/user-api-key/undo-revoke" => "user_api_keys#undo_revoke" get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new diff --git a/db/migrate/20160816052836_user_api_client_id_is_unique.rb b/db/migrate/20160816052836_user_api_client_id_is_unique.rb new file mode 100644 index 00000000000..59b3dc4b354 --- /dev/null +++ b/db/migrate/20160816052836_user_api_client_id_is_unique.rb @@ -0,0 +1,6 @@ +class UserApiClientIdIsUnique < ActiveRecord::Migration + def change + remove_index :user_api_keys, [:client_id] + add_index :user_api_keys, [:client_id], unique: true + end +end diff --git a/db/migrate/20160816063534_add_revoked_at_to_user_api_keys.rb b/db/migrate/20160816063534_add_revoked_at_to_user_api_keys.rb new file mode 100644 index 00000000000..30c252a6e86 --- /dev/null +++ b/db/migrate/20160816063534_add_revoked_at_to_user_api_keys.rb @@ -0,0 +1,5 @@ +class AddRevokedAtToUserApiKeys < ActiveRecord::Migration + def change + add_column :user_api_keys, :revoked_at, :datetime + end +end diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index dc74a30005e..e0d96aea08e 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -177,7 +177,7 @@ class Auth::DefaultCurrentUserProvider protected def lookup_user_api_user(user_api_key) - if api_key = UserApiKey.where(key: user_api_key).includes(:user).first + if api_key = UserApiKey.where(key: user_api_key, revoked_at: nil).includes(:user).first if !api_key.write && @env["REQUEST_METHOD"] != "GET" raise Discourse::InvalidAccess end diff --git a/spec/controllers/user_api_keys_controller_spec.rb b/spec/controllers/user_api_keys_controller_spec.rb index b4c86b10f0a..60eb2910624 100644 --- a/spec/controllers/user_api_keys_controller_spec.rb +++ b/spec/controllers/user_api_keys_controller_spec.rb @@ -127,6 +127,11 @@ TXT uri.query = "" expect(uri.to_s).to eq(args[:auth_redirect] + "?") + # should overwrite if needed + args["access"] = "pr" + post :create, args + + expect(response.code).to eq("302") end end