basic api support

This commit is contained in:
Sam 2013-03-25 18:04:28 -07:00
parent a177264114
commit c57ec611e1
16 changed files with 144 additions and 1 deletions

View File

@ -0,0 +1,3 @@
Discourse.AdminApiController = Ember.Controller.extend({
});

View File

@ -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');
}
});

View File

@ -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();
}
});

View File

@ -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' });

View File

@ -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'>

View File

@ -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>

View File

@ -0,0 +1,3 @@
Discourse.AdminApiView = Discourse.View.extend({
templateName: 'admin/templates/api'
});

View File

@ -48,7 +48,8 @@ Discourse.Model = Ember.Object.extend(Discourse.Presence, {
_this.set(k, v);
}
});
}
}
});
Discourse.Model.reopenClass({

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -731,6 +731,8 @@ en:
flagged_by: "Flagged by"
error: "Something went wrong"
api:
title: "API"
customize:
title: "Customize"
header: "Header"

View File

@ -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"

View File

@ -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'

View File

@ -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

View File

@ -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