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('email_logs', { path: '/email_logs' });
|
||||
this.route('customize', { path: '/customize' });
|
||||
this.route('api', {path: '/api'});
|
||||
|
||||
this.resource('adminReports', { path: '/reports/:type' });
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<li>{{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.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.api'}}{{i18n admin.api.title}}{{/linkTo}}</li>
|
||||
</ul>
|
||||
|
||||
<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'
|
||||
});
|
|
@ -49,6 +49,7 @@ Discourse.Model = Ember.Object.extend(Discourse.Presence, {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
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
|
||||
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?)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ class SiteSetting < ActiveRecord::Base
|
|||
setting(:company_full_name, 'My Unconfigured Forum Ltd.')
|
||||
setting(:company_short_name, 'Unconfigured Forum')
|
||||
setting(:company_domain, 'www.example.com')
|
||||
setting(:api_key, '')
|
||||
client_setting(:traditional_markdown_linebreaks, false)
|
||||
client_setting(:top_menu, 'popular|new|unread|favorited|categories')
|
||||
client_setting(:post_menu, 'like|edit|flag|delete|share|bookmark|reply')
|
||||
|
@ -168,6 +169,15 @@ class SiteSetting < ActiveRecord::Base
|
|||
|
||||
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?
|
||||
self.enforce_global_nicknames? && self.discourse_org_access_key.present?
|
||||
end
|
||||
|
|
|
@ -731,6 +731,8 @@ en:
|
|||
flagged_by: "Flagged by"
|
||||
error: "Something went wrong"
|
||||
|
||||
api:
|
||||
title: "API"
|
||||
customize:
|
||||
title: "Customize"
|
||||
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_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"
|
||||
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"
|
||||
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"
|
||||
|
|
|
@ -56,6 +56,11 @@ Discourse::Application.routes.draw do
|
|||
resources :export
|
||||
get 'version_check' => 'versions#show'
|
||||
resources :dashboard, only: [:index]
|
||||
resources :api, only: [:index] do
|
||||
collection do
|
||||
post 'generate_key'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get 'email_preferences' => 'email#preferences_redirect'
|
||||
|
|
|
@ -52,6 +52,18 @@ module CurrentUser
|
|||
@current_user.update_last_seen!
|
||||
@current_user.update_ip_address!(request.remote_ip)
|
||||
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
|
||||
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