basic api support
This commit is contained in:
parent
a177264114
commit
c57ec611e1
|
@ -0,0 +1,3 @@
|
||||||
|
Discourse.AdminApiController = Ember.Controller.extend({
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
Discourse.AdminApi = Discourse.Model.extend({
|
||||||
|
VALID_KEY_LENGTH: 64,
|
||||||
|
|
||||||
|
keyExists: function(){
|
||||||
|
var key = this.get('key') || '';
|
||||||
|
return key && key.length === this.VALID_KEY_LENGTH;
|
||||||
|
}.property('key'),
|
||||||
|
|
||||||
|
generateKey: function(){
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
$.ajax(Discourse.getURL('/admin/api/generate_key'),{
|
||||||
|
type: 'POST'
|
||||||
|
}).success(function(result){
|
||||||
|
_this.set('key', result.key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Discourse.AdminApi.reopenClass({
|
||||||
|
find: function(){
|
||||||
|
return this.getAjax('/admin/api');
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
Handles routes related to api
|
||||||
|
|
||||||
|
@class AdminApiRoute
|
||||||
|
@extends Discourse.Route
|
||||||
|
@namespace Discourse
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.AdminApiRoute = Discourse.Route.extend({
|
||||||
|
renderTemplate: function() {
|
||||||
|
this.render({into: 'admin/templates/admin'});
|
||||||
|
},
|
||||||
|
|
||||||
|
model: function(params) {
|
||||||
|
return Discourse.AdminApi.find();
|
||||||
|
}
|
||||||
|
});
|
|
@ -10,6 +10,7 @@ Discourse.Route.buildRoutes(function() {
|
||||||
this.route('site_settings', { path: '/site_settings' });
|
this.route('site_settings', { path: '/site_settings' });
|
||||||
this.route('email_logs', { path: '/email_logs' });
|
this.route('email_logs', { path: '/email_logs' });
|
||||||
this.route('customize', { path: '/customize' });
|
this.route('customize', { path: '/customize' });
|
||||||
|
this.route('api', {path: '/api'});
|
||||||
|
|
||||||
this.resource('adminReports', { path: '/reports/:type' });
|
this.resource('adminReports', { path: '/reports/:type' });
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<li>{{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li>
|
||||||
|
<li>{{#linkTo 'admin.api'}}{{i18n admin.api.title}}{{/linkTo}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class='boxed white admin-content'>
|
<div class='boxed white admin-content'>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!-- Hold off on localizing for a few days while I finalize this page -->
|
||||||
|
<h3>API Information</h3>
|
||||||
|
{{#if content.keyExists}}
|
||||||
|
<strong>Key:</strong> {{content.key}} <button {{action regenerateKey}}>Regenerate API Key</button>
|
||||||
|
<p>Keep this key <strong>secret</strong>, all users that have it may create arbirary posts on the forum as any user.</p>
|
||||||
|
{{else}}
|
||||||
|
<p>Your API key will allow you to create and update topics using JSON calls.</p>
|
||||||
|
<button {{action generateKey target="content"}}>Generate API Key</button>
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Discourse.AdminApiView = Discourse.View.extend({
|
||||||
|
templateName: 'admin/templates/api'
|
||||||
|
});
|
|
@ -48,7 +48,8 @@ Discourse.Model = Ember.Object.extend(Discourse.Presence, {
|
||||||
_this.set(k, v);
|
_this.set(k, v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Discourse.Model.reopenClass({
|
Discourse.Model.reopenClass({
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
class Admin::ApiController < Admin::AdminController
|
||||||
|
def index
|
||||||
|
render json: {key: SiteSetting.api_key}
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_key
|
||||||
|
SiteSetting.generate_api_key!
|
||||||
|
render json: {key: SiteSetting.api_key}
|
||||||
|
end
|
||||||
|
end
|
|
@ -250,6 +250,11 @@ class ApplicationController < ActionController::Base
|
||||||
|
|
||||||
def check_xhr
|
def check_xhr
|
||||||
unless (controller_name == 'forums' || controller_name == 'user_open_ids')
|
unless (controller_name == 'forums' || controller_name == 'user_open_ids')
|
||||||
|
# bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying
|
||||||
|
if !request.get? && request["api_key"]
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
raise RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?)
|
raise RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,7 @@ class SiteSetting < ActiveRecord::Base
|
||||||
setting(:company_full_name, 'My Unconfigured Forum Ltd.')
|
setting(:company_full_name, 'My Unconfigured Forum Ltd.')
|
||||||
setting(:company_short_name, 'Unconfigured Forum')
|
setting(:company_short_name, 'Unconfigured Forum')
|
||||||
setting(:company_domain, 'www.example.com')
|
setting(:company_domain, 'www.example.com')
|
||||||
|
setting(:api_key, '')
|
||||||
client_setting(:traditional_markdown_linebreaks, false)
|
client_setting(:traditional_markdown_linebreaks, false)
|
||||||
client_setting(:top_menu, 'popular|new|unread|favorited|categories')
|
client_setting(:top_menu, 'popular|new|unread|favorited|categories')
|
||||||
client_setting(:post_menu, 'like|edit|flag|delete|share|bookmark|reply')
|
client_setting(:post_menu, 'like|edit|flag|delete|share|bookmark|reply')
|
||||||
|
@ -168,6 +169,15 @@ class SiteSetting < ActiveRecord::Base
|
||||||
|
|
||||||
setting(:max_similar_results, 7)
|
setting(:max_similar_results, 7)
|
||||||
|
|
||||||
|
def self.generate_api_key!
|
||||||
|
self.api_key = SecureRandom.hex(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.api_key_valid?(tested)
|
||||||
|
t = tested.strip
|
||||||
|
t.length == 64 && t == self.api_key
|
||||||
|
end
|
||||||
|
|
||||||
def self.call_discourse_hub?
|
def self.call_discourse_hub?
|
||||||
self.enforce_global_nicknames? && self.discourse_org_access_key.present?
|
self.enforce_global_nicknames? && self.discourse_org_access_key.present?
|
||||||
end
|
end
|
||||||
|
|
|
@ -731,6 +731,8 @@ en:
|
||||||
flagged_by: "Flagged by"
|
flagged_by: "Flagged by"
|
||||||
error: "Something went wrong"
|
error: "Something went wrong"
|
||||||
|
|
||||||
|
api:
|
||||||
|
title: "API"
|
||||||
customize:
|
customize:
|
||||||
title: "Customize"
|
title: "Customize"
|
||||||
header: "Header"
|
header: "Header"
|
||||||
|
|
|
@ -316,6 +316,7 @@ en:
|
||||||
company_full_name: "The full name of the company that runs this site, used in legal documents like the /tos"
|
company_full_name: "The full name of the company that runs this site, used in legal documents like the /tos"
|
||||||
company_short_name: "The short name of the company that runs this site, used in legal documents like the /tos"
|
company_short_name: "The short name of the company that runs this site, used in legal documents like the /tos"
|
||||||
company_domain: "The domain name owned by the company that runs this site, used in legal documents like the /tos"
|
company_domain: "The domain name owned by the company that runs this site, used in legal documents like the /tos"
|
||||||
|
api_key: "The secure API key used to create and update topics, use the /admin/api section to set it up"
|
||||||
access_password: "When restricted access is enabled, this password must be entered"
|
access_password: "When restricted access is enabled, this password must be entered"
|
||||||
queue_jobs: "Queue various jobs in sidekiq, if false queues are inline"
|
queue_jobs: "Queue various jobs in sidekiq, if false queues are inline"
|
||||||
crawl_images: "Enable retrieving images from third party sources to insert width and height dimensions"
|
crawl_images: "Enable retrieving images from third party sources to insert width and height dimensions"
|
||||||
|
|
|
@ -56,6 +56,11 @@ Discourse::Application.routes.draw do
|
||||||
resources :export
|
resources :export
|
||||||
get 'version_check' => 'versions#show'
|
get 'version_check' => 'versions#show'
|
||||||
resources :dashboard, only: [:index]
|
resources :dashboard, only: [:index]
|
||||||
|
resources :api, only: [:index] do
|
||||||
|
collection do
|
||||||
|
post 'generate_key'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get 'email_preferences' => 'email#preferences_redirect'
|
get 'email_preferences' => 'email#preferences_redirect'
|
||||||
|
|
|
@ -52,6 +52,18 @@ module CurrentUser
|
||||||
@current_user.update_last_seen!
|
@current_user.update_last_seen!
|
||||||
@current_user.update_ip_address!(request.remote_ip)
|
@current_user.update_ip_address!(request.remote_ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# possible we have an api call, impersonate
|
||||||
|
unless @current_user
|
||||||
|
if api_key = request["api_key"]
|
||||||
|
if api_username = request["api_username"]
|
||||||
|
if SiteSetting.api_key_valid?(api_key)
|
||||||
|
@current_user = User.where(username_lower: api_username.downcase).first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@current_user
|
@current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe 'api' do
|
||||||
|
before do
|
||||||
|
fake_key = SecureRandom.hex(32)
|
||||||
|
SiteSetting.stubs(:api_key).returns(fake_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe PostsController do
|
||||||
|
let(:user) do
|
||||||
|
Fabricate(:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:post) do
|
||||||
|
Fabricate(:post)
|
||||||
|
end
|
||||||
|
|
||||||
|
# choosing an arbitrarily easy to mock trusted activity
|
||||||
|
it 'allows users with api key to bookmark posts' do
|
||||||
|
PostAction.expects(:act).with(user,post,PostActionType.types[:bookmark]).returns(true)
|
||||||
|
put :bookmark, bookmarked: "true" ,post_id: post.id , api_key: SiteSetting.api_key, api_username: user.username
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'disallows phonies to bookmark posts' do
|
||||||
|
lambda do
|
||||||
|
put :bookmark, bookmarked: "true" ,post_id: post.id , api_key: SecureRandom.hex(32), api_username: user.username
|
||||||
|
end.should raise_error Discourse::NotLoggedIn
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'disallows blank api' do
|
||||||
|
SiteSetting.stubs(:api_key).returns("")
|
||||||
|
lambda do
|
||||||
|
put :bookmark, bookmarked: "true" ,post_id: post.id , api_key: "", api_username: user.username
|
||||||
|
end.should raise_error Discourse::NotLoggedIn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue