API Keys and Authorization Framework
This commit is contained in:
parent
9819bc3cfd
commit
213e5c1780
2
Gemfile
2
Gemfile
|
@ -10,7 +10,7 @@ gem 'httparty'
|
|||
|
||||
gem 'devise'
|
||||
gem 'warden'
|
||||
gem 'doorkeeper', github: 'shinzui/doorkeeper'
|
||||
gem 'rack-cors', :require => 'rack/cors'
|
||||
gem 'cancan'
|
||||
gem 'rolify', :github => 'EppO/rolify'
|
||||
|
||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -46,13 +46,6 @@ GIT
|
|||
origin (~> 2.0)
|
||||
tzinfo (~> 0.3.37)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/shinzui/doorkeeper.git
|
||||
revision: 8f67bd06945983cf9fd1b883a09cb51bc9ffe32e
|
||||
specs:
|
||||
doorkeeper (1.0.0.rc1)
|
||||
railties (>= 3.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
|
@ -154,6 +147,8 @@ GEM
|
|||
puma (2.7.1)
|
||||
rack (>= 1.1, < 2.0)
|
||||
rack (1.5.2)
|
||||
rack-cors (0.2.7)
|
||||
rack
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rails (4.0.2)
|
||||
|
@ -218,7 +213,6 @@ DEPENDENCIES
|
|||
carrierwave-mongoid
|
||||
coffee-rails (~> 4.0.0)
|
||||
devise
|
||||
doorkeeper!
|
||||
font-awesome-rails!
|
||||
httparty
|
||||
jbuilder (~> 1.2)
|
||||
|
@ -228,6 +222,7 @@ DEPENDENCIES
|
|||
mongoid-grid_fs!
|
||||
mongoid_slug!
|
||||
puma
|
||||
rack-cors
|
||||
rails (= 4.0.2)
|
||||
rmagick
|
||||
rolify!
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
|
@ -0,0 +1,3 @@
|
|||
// Place all the styles related to the clients controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
|
@ -0,0 +1,19 @@
|
|||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'oauth/token.json',
|
||||
# url: 'http://api.geonames.org/citiesJSON',
|
||||
dataType: 'JSON',
|
||||
data: {
|
||||
grant_type: 'client_credentials',
|
||||
client_id: '8dae7e34b1ba624e601cf659b65a70fa92d1c408d1f18252f9c0119b3efdce8d',
|
||||
client_secret: 'e11386baaa4cd9a2327ce3a170ec7ae74d88c5ed618342852492f7603e065cb9'
|
||||
},
|
||||
success: (d, textStatus, jqXHR) ->
|
||||
console.debug d, textStatus
|
||||
error: (jqXHR, textStatus, errorThrown) ->
|
||||
console.debug jqXHR, textStatus, errorThrown
|
||||
})
|
|
@ -0,0 +1,69 @@
|
|||
body {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
p, ol, ul, td {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
&:visited {
|
||||
color: #666;
|
||||
}
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
&.field, &.actions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#notice {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.field_with_errors {
|
||||
padding: 2px;
|
||||
background-color: red;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#error_explanation {
|
||||
width: 450px;
|
||||
border: 2px solid red;
|
||||
padding: 7px;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f0f0f0;
|
||||
h2 {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding: 5px 5px 5px 15px;
|
||||
font-size: 12px;
|
||||
margin: -7px;
|
||||
margin-bottom: 0px;
|
||||
background-color: #c00;
|
||||
color: #fff;
|
||||
}
|
||||
ul li {
|
||||
font-size: 12px;
|
||||
list-style: square;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
module Api
|
||||
module V1
|
||||
class BaseController < ApplicationController
|
||||
before_filter :restrict_access
|
||||
respond_to :json
|
||||
skip_before_filter :verify_authenticity_token
|
||||
|
||||
|
||||
def current_resource_owner
|
||||
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
|
||||
end
|
||||
|
||||
private
|
||||
def authorize_client
|
||||
verify_client || render_unauthorized
|
||||
end
|
||||
|
||||
def restrict_access
|
||||
authenticate_or_request_with_http_token do |token, options|
|
||||
ApiKey.pluck(:access_token).include?(token)
|
||||
end
|
||||
end
|
||||
|
||||
def verify_client
|
||||
site_token = request.headers[:HTTP_X_SITETOKEN]
|
||||
site_id = request.headers[:HTTP_X_SITEID]
|
||||
client_status = Client.where(site_token: site_token).where(site_id: site_id).present?
|
||||
end
|
||||
|
||||
def render_unauthorized
|
||||
self.headers['WWW-Authenticate'] = 'Token realm="Application"'
|
||||
render json: 'Bad credentials', status: 401
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module Api
|
||||
module V1
|
||||
class ClientsController < Api::V1::BaseController
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
respond_with Client.create(client_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def client_params
|
||||
params.require(:client).permit(:site_name, :site_token, :site_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
module V1
|
||||
class ExtensionsController < ApplicationController
|
||||
# doorkeeper_for :all
|
||||
class ExtensionsController < Api::V1::BaseController
|
||||
before_action :authorize_client
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
module Api
|
||||
module V1
|
||||
class TemplatesController < ApplicationController
|
||||
# doorkeeper_for :all, :scopes => [:public]
|
||||
class TemplatesController < Api::V1::BaseController
|
||||
before_action :authorize_client
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
# Prevent CSRF attacks by raising an exception.
|
||||
# For APIs, you may want to use :null_session instead.
|
||||
protect_from_forgery with: :exception
|
||||
protect_from_forgery with: :null_session
|
||||
end
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
class ClientsController < ApplicationController
|
||||
before_action :set_client, only: [:show, :edit, :update, :destroy]
|
||||
|
||||
# GET /clients
|
||||
# GET /clients.json
|
||||
def index
|
||||
@clients = Client.all
|
||||
end
|
||||
|
||||
# GET /clients/1
|
||||
# GET /clients/1.json
|
||||
def show
|
||||
end
|
||||
|
||||
# GET /clients/new
|
||||
def new
|
||||
@client = Client.new
|
||||
end
|
||||
|
||||
# GET /clients/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /clients
|
||||
# POST /clients.json
|
||||
def create
|
||||
@client = Client.new(client_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @client.save
|
||||
format.html { redirect_to @client, notice: 'Client was successfully created.' }
|
||||
format.json { render action: 'show', status: :created, location: @client }
|
||||
else
|
||||
format.html { render action: 'new' }
|
||||
format.json { render json: @client.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /clients/1
|
||||
# PATCH/PUT /clients/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @client.update(client_params)
|
||||
format.html { redirect_to @client, notice: 'Client was successfully updated.' }
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.html { render action: 'edit' }
|
||||
format.json { render json: @client.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /clients/1
|
||||
# DELETE /clients/1.json
|
||||
def destroy
|
||||
@client.destroy
|
||||
respond_to do |format|
|
||||
format.html { redirect_to clients_url }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_client
|
||||
@client = Client.find(params[:id])
|
||||
end
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def client_params
|
||||
params.require(:client).permit(:site_name, :site_token, :site_id)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
module ClientsHelper
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class ApiKey
|
||||
include Mongoid::Document
|
||||
field :access_token, type: String
|
||||
|
||||
index({ access_token: 1}, { unique: true })
|
||||
|
||||
before_create :generate_access_token
|
||||
|
||||
private
|
||||
|
||||
def generate_access_token
|
||||
begin
|
||||
self.access_token = SecureRandom.hex
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class Client
|
||||
include Mongoid::Document
|
||||
field :site_name, type: String
|
||||
field :site_token, type: String
|
||||
field :site_id, type: String
|
||||
|
||||
validates :site_id, :uniqueness => true
|
||||
validates :site_token, :uniqueness => true
|
||||
end
|
|
@ -4,7 +4,7 @@ class User
|
|||
# Include default devise modules. Others available are:
|
||||
# :confirmable, :lockable, :timeoutable and :omniauthable
|
||||
devise :database_authenticatable,
|
||||
:recoverable, :rememberable, :trackable, :validatable
|
||||
:recoverable, :rememberable, :trackable, :validatable, :registerable
|
||||
|
||||
## Database authenticatable
|
||||
field :email, :type => String, :default => ""
|
||||
|
@ -35,4 +35,10 @@ class User
|
|||
# field :unlock_token, :type => String # Only if unlock strategy is :email or :both
|
||||
# field :locked_at, :type => Time
|
||||
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner
|
||||
|
||||
def self.authenticate!(email, password)
|
||||
user = User.where(email: email).first
|
||||
return (user.valid_password?(password) ? user : nil) unless user.nil?
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<%= form_for(@client) do |f| %>
|
||||
<% if @client.errors.any? %>
|
||||
<div id="error_explanation">
|
||||
<h2><%= pluralize(@client.errors.count, "error") %> prohibited this client from being saved:</h2>
|
||||
|
||||
<ul>
|
||||
<% @client.errors.full_messages.each do |msg| %>
|
||||
<li><%= msg %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :site_name %><br>
|
||||
<%= f.text_field :site_name %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label :site_token %><br>
|
||||
<%= f.text_field :site_token %>
|
||||
</div>
|
||||
<div class="field">
|
||||
<%= f.label :site_id %><br>
|
||||
<%= f.text_field :site_id %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<%= f.submit %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -0,0 +1,6 @@
|
|||
<h1>Editing client</h1>
|
||||
|
||||
<%= render 'form' %>
|
||||
|
||||
<%= link_to 'Show', @client %> |
|
||||
<%= link_to 'Back', clients_path %>
|
|
@ -0,0 +1,31 @@
|
|||
<h1>Listing clients</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Site name</th>
|
||||
<th>Site token</th>
|
||||
<th>Site</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% @clients.each do |client| %>
|
||||
<tr>
|
||||
<td><%= client.site_name %></td>
|
||||
<td><%= client.site_token %></td>
|
||||
<td><%= client.site_id %></td>
|
||||
<td><%= link_to 'Show', client %></td>
|
||||
<td><%= link_to 'Edit', edit_client_path(client) %></td>
|
||||
<td><%= link_to 'Destroy', client, method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<%= link_to 'New Client', new_client_path %>
|
|
@ -0,0 +1,4 @@
|
|||
json.array!(@clients) do |client|
|
||||
json.extract! client, :id, :site_name, :site_token, :site_id
|
||||
json.url client_url(client, format: :json)
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
<h1>New client</h1>
|
||||
|
||||
<%= render 'form' %>
|
||||
|
||||
<%= link_to 'Back', clients_path %>
|
|
@ -0,0 +1,19 @@
|
|||
<p id="notice"><%= notice %></p>
|
||||
|
||||
<p>
|
||||
<strong>Site name:</strong>
|
||||
<%= @client.site_name %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Site token:</strong>
|
||||
<%= @client.site_token %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Site:</strong>
|
||||
<%= @client.site_id %>
|
||||
</p>
|
||||
|
||||
<%= link_to 'Edit', edit_client_path(@client) %> |
|
||||
<%= link_to 'Back', clients_path %>
|
|
@ -0,0 +1 @@
|
|||
json.extract! @client, :id, :site_name, :site_token, :site_id, :created_at, :updated_at
|
|
@ -24,5 +24,8 @@ module Mtstore
|
|||
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
||||
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
||||
# config.i18n.default_locale = :de
|
||||
config.to_prepare do
|
||||
DeviseController.respond_to :html, :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,11 +3,16 @@ Doorkeeper.configure do
|
|||
# Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
|
||||
orm :mongoid4
|
||||
|
||||
resource_owner_authenticator do |routes|
|
||||
current_user || warden.authenticate!(:scope => :user)
|
||||
end
|
||||
|
||||
# This block will be called to check whether the resource owner is authenticated or not.
|
||||
resource_owner_from_credentials do |routes|
|
||||
request.params[:user] = {:email => request.params[:username], :password => request.params[:password]}
|
||||
request.env["devise.allow_params_authentication"] = true
|
||||
request.env["warden"].authenticate!(:scope => :user)
|
||||
# User.authenticate!(params[:username], params[:password])
|
||||
end
|
||||
|
||||
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
|
||||
|
|
|
@ -2,17 +2,19 @@ require 'api_constraints'
|
|||
|
||||
Mtstore::Application.routes.draw do
|
||||
|
||||
resources :clients
|
||||
|
||||
# get "search/index"
|
||||
# get "search/show"
|
||||
use_doorkeeper do
|
||||
controllers :applications => 'oauth/applications'
|
||||
end
|
||||
|
||||
devise_for :users
|
||||
namespace :api, defaults: {format: 'json'} do
|
||||
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: :true) do
|
||||
resources :templates
|
||||
resources :extensions
|
||||
resources :clients do
|
||||
post 'verify_client', on: :collection
|
||||
end
|
||||
end
|
||||
end
|
||||
resources :templates
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
response = curl -i "http://localhost:3000/oauth/token" -F grant_type=password -F client_id='8dae7e34b1ba624e601cf659b65a70fa92d1c408d1f18252f9c0119b3efdce8d' -F client_secret='e11386baaa4cd9a2327ce3a170ec7ae74d88c5ed618342852492f7603e065cb9' -F username="orbit@rulingcom.com" -F password="bjo4xjp6"
|
||||
puts response
|
|
@ -0,0 +1,49 @@
|
|||
require 'test_helper'
|
||||
|
||||
class ClientsControllerTest < ActionController::TestCase
|
||||
setup do
|
||||
@client = clients(:one)
|
||||
end
|
||||
|
||||
test "should get index" do
|
||||
get :index
|
||||
assert_response :success
|
||||
assert_not_nil assigns(:clients)
|
||||
end
|
||||
|
||||
test "should get new" do
|
||||
get :new
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should create client" do
|
||||
assert_difference('Client.count') do
|
||||
post :create, client: { site_id: @client.site_id, site_name: @client.site_name, site_token: @client.site_token }
|
||||
end
|
||||
|
||||
assert_redirected_to client_path(assigns(:client))
|
||||
end
|
||||
|
||||
test "should show client" do
|
||||
get :show, id: @client
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should get edit" do
|
||||
get :edit, id: @client
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should update client" do
|
||||
patch :update, id: @client, client: { site_id: @client.site_id, site_name: @client.site_name, site_token: @client.site_token }
|
||||
assert_redirected_to client_path(assigns(:client))
|
||||
end
|
||||
|
||||
test "should destroy client" do
|
||||
assert_difference('Client.count', -1) do
|
||||
delete :destroy, id: @client
|
||||
end
|
||||
|
||||
assert_redirected_to clients_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
access_token: MyString
|
||||
|
||||
two:
|
||||
access_token: MyString
|
|
@ -0,0 +1,11 @@
|
|||
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
site_name: MyString
|
||||
site_token: MyString
|
||||
site_id: MyString
|
||||
|
||||
two:
|
||||
site_name: MyString
|
||||
site_token: MyString
|
||||
site_id: MyString
|
|
@ -0,0 +1,4 @@
|
|||
require 'test_helper'
|
||||
|
||||
class ClientsHelperTest < ActionView::TestCase
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
require 'test_helper'
|
||||
|
||||
class ApiKeyTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
require 'test_helper'
|
||||
|
||||
class ClientTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
Loading…
Reference in New Issue