Commit 910a8aaa authored by Nina's avatar Nina

Introduce controller layer

Pull all authorization logic into the controller as well as access to
models and use it in the web API.
parent ababe58d
Pipeline #21790 passed with stages
in 3 minutes and 57 seconds
......@@ -48,6 +48,11 @@ class SchleuderApiDaemon < Sinatra::Base
ActiveRecord::Base.connection.close
end
error Errors::Unauthorized do
status 403
body('Not authorized')
end
error do
exc = env['sinatra.error']
logger.error "Error: #{env['sinatra.error'].message}"
......
......@@ -4,45 +4,41 @@ class SchleuderApiDaemon < Sinatra::Base
namespace '/keys' do
get '.json' do
require_list_id_param
list = load_list(params[:list_id])
authorized?(list, :list_keys)
keys = list.keys.sort_by(&:email).map do |key|
keys = keys_controller.find_all(params[:list_id])
keys_hash = keys.sort_by(&:email).map do |key|
key_to_hash(key)
end
json keys
json keys_hash
end
post '.json' do
list = load_list(requested_list_id)
authorized?(list, :add_keys)
input = parsed_body['keymaterial']
if ! input.match('BEGIN PGP')
if !input.match('BEGIN PGP')
input = Base64.decode64(input)
end
json list.import_key(input)
json keys_controller.import(requested_list_id, input)
end
get '/check_keys.json' do
require_list_id_param
list = load_list(params[:list_id])
authorized?(list, :check_keys)
json result: list.check_keys
json result: keys_controller.check(params[:list_id])
end
get '/:fingerprint.json' do |fingerprint|
require_list_id_param
list = load_list(params[:list_id])
key = list.key(fingerprint) || halt(404)
authorized?(key, :read)
key = keys_controller.find(params[:list_id], fingerprint)
json key_to_hash(key, true)
end
delete '/:fingerprint.json' do |fingerprint|
require_list_id_param
list = load_list(params[:list_id])
key = list.key(fingerprint) || halt(404)
authorized?(key, :delete)
key.delete!
keys_controller.delete(params[:list_id], fingerprint)
end
end
private
def keys_controller
Schleuder::KeysController.new(current_account)
end
end
......@@ -3,18 +3,16 @@ class SchleuderApiDaemon < Sinatra::Base
namespace '/lists' do
get '.json' do
json current_account.scoped(List), include: :subscriptions
json(lists_controller.find_all, include: :subscriptions)
end
post '.json' do
authorized?(List, :create)
listname = parsed_body['email']
fingerprint = parsed_body['fingerprint']
adminaddress = parsed_body['adminaddress']
adminfingerprint = parsed_body['adminfingerprint']
adminkey = parsed_body['adminkey']
list, messages = ListBuilder.new({email: listname, fingerprint: fingerprint}, adminaddress, adminfingerprint, adminkey).run
list, messages = lists_controller.create(listname, fingerprint, adminaddress, adminfingerprint, adminkey)
if list.nil?
client_error(messages, 422)
elsif ! list.valid?
......@@ -26,30 +24,24 @@ class SchleuderApiDaemon < Sinatra::Base
end
get '/configurable_attributes.json' do
json(List.configurable_attributes) + "\n"
json(lists_controller.get_configurable_attributes) + "\n"
end
post '/send_list_key_to_subscriptions.json' do
require_list_id_param
list = load_list(params[:list_id])
authorized?(list, :send_list_key)
json(result: list.send_list_key_to_subscriptions)
json(result: lists_controller.send_list_key_to_subscriptions(params[:list_id]))
end
get '/new.json' do
json List.new
json lists_controller.new_list
end
get '/:id.json' do |id|
list = load_list(id)
authorized?(list, :read)
json(list)
json lists_controller.find(id)
end
put '/:id.json' do |id|
list = load_list(id)
authorized?(list, :update)
if list.update(parsed_body)
if lists_controller.update(id, parsed_body)
204
else
client_error(list)
......@@ -57,9 +49,7 @@ class SchleuderApiDaemon < Sinatra::Base
end
patch '/:id.json' do |id|
list = load_list(id)
authorized?(list, :update)
if list.update(parsed_body)
if lists_controller.update(id, parsed_body)
204
else
client_error(list)
......@@ -67,13 +57,15 @@ class SchleuderApiDaemon < Sinatra::Base
end
delete '/:id.json' do |id|
list = load_list(id)
authorized?(list, :delete)
if list.destroy
if lists_controller.delete(id)
200
else
client_error(list)
end
end
end
def lists_controller
Schleuder::ListsController.new(current_account)
end
end
......@@ -11,7 +11,7 @@ class SchleuderApiDaemon < Sinatra::Base
logger.debug "Subscription filter: #{filter.inspect}"
if filter['list_id'] && ! is_an_integer?(filter['list_id'])
# Value is an email-address
if list = List.where(email: filter['list_id']).first
if list = lists_controller.find(filter['list_id'])
filter['list_id'] = list.id
else
status 404
......@@ -19,31 +19,21 @@ class SchleuderApiDaemon < Sinatra::Base
end
end
authorized?(Subscription, :list)
subscriptions = current_account.scoped(Subscription).where(filter)
json subscriptions
json subscriptions_controller.find_all(filter)
end
post '.json' do
begin
list = load_list(requested_list_id)
authorized?(list, :subscribe)
# We don't have to care about nil-values, subscribe() does that for us.
sub, msgs = list.subscribe(
parsed_body['email'],
parsed_body['fingerprint'],
parsed_body['admin'],
parsed_body['delivery_enabled'],
find_key_material
)
set_x_messages(msgs)
logger.debug "subcription: #{sub.inspect}"
if sub.valid?
logger.debug "Subscribed: #{sub.inspect}"
attributes = find_attributes_from_body(%w[email fingerprint admin delivery_enabled])
subscription, messages = subscriptions_controller.subscribe(requested_list_id, attributes, find_key_material)
set_x_messages(messages)
logger.debug "subcription: #{subscription.inspect}"
if subscription.valid?
logger.debug "Subscribed: #{subscription.inspect}"
# TODO: why redirect instead of respond with result?
redirect to("/subscriptions/#{sub.id}.json"), 201
redirect to("/subscriptions/#{subscription.id}.json"), 201
else
client_error(sub, 422)
client_error(subscription, 422)
end
rescue ActiveRecord::RecordNotUnique
logger.error 'Already subscribed'
......@@ -53,17 +43,15 @@ class SchleuderApiDaemon < Sinatra::Base
end
get '/configurable_attributes.json' do
json(Subscription.configurable_attributes) + "\n"
json(subscriptions_controller.get_configurable_attributes) + "\n"
end
get '/new.json' do
json Subscription.new
json subscriptions_controller.new_subscription
end
get '/:id.json' do |id|
subscription = load_subscription(id)
authorized?(subscription, :read)
json subscription
json subscriptions_controller.find(id)
end
put '/:id.json' do |id|
......@@ -86,23 +74,31 @@ class SchleuderApiDaemon < Sinatra::Base
end
patch '/:id.json' do |id|
sub = load_subscription(id)
authorized?(sub, :update)
if sub.update(parsed_body)
subscription = subscriptions_controller.update(id, parsed_body)
if subscription
200
else
client_error(sub)
client_error(subscription)
end
end
delete '/:id.json' do |id|
sub = load_subscription(id)
authorized?(sub, :delete)
if sub.destroy
subscription = subscriptions_controller.delete(id)
if subscription
200
else
client_error(sub)
client_error(subscription)
end
end
end
private
def subscriptions_controller
Schleuder::SubscriptionsController.new(current_account)
end
def lists_controller
Schleuder::ListsController.new(current_account)
end
end
......@@ -64,6 +64,11 @@ require 'schleuder/authorizer_policies/list_policy'
require 'schleuder/authorizer_policies/key_policy'
require 'schleuder/authorizer'
require 'schleuder/controllers/base_controller'
require 'schleuder/controllers/keys_controller'
require 'schleuder/controllers/lists_controller'
require 'schleuder/controllers/subscriptions_controller'
# Setup
ENV['SCHLEUDER_CONFIG'] ||= '/etc/schleuder/schleuder.yml'
ENV['SCHLEUDER_LIST_DEFAULTS'] ||= '/etc/schleuder/list-defaults.yml'
......
module Schleuder
class BaseController
attr_reader :current_account
def initialize(current_account)
@current_account = current_account
end
private
def authorized?(resource, action)
current_account.authorized?(resource, action) || raise(Errors::Unauthorized.new)
end
def get_list_by_id_or_email(identifier)
query_args = to_query_args(identifier)
List.where(query_args).first
end
def to_query_args(identifier)
if is_an_integer?(identifier)
{id: identifier.to_i}
else
{email: identifier.to_s}
end
end
def is_an_integer?(input)
input.to_s.match(/^[0-9]+$/).present?
end
end
end
module Schleuder
class KeysController < BaseController
def find_all(list_id)
list = get_list_by_id_or_email(list_id)
authorized?(list, :list_keys)
list.keys
end
def import(list_id, key)
list = get_list_by_id_or_email(list_id)
authorized?(list, :add_keys)
list.import_key(key)
end
def check(list_id)
list = get_list_by_id_or_email(list_id)
authorized?(list, :check_keys)
list.check_keys
end
def find(list_id, fingerprint)
list = get_list_by_id_or_email(list_id)
key = list.key(fingerprint)
authorized?(key, :read)
key
end
def delete(list_id, fingerprint)
list = get_list_by_id_or_email(list_id)
key = list.key(fingerprint) || halt(404)
authorized?(key, :delete)
key.delete!
end
end
end
module Schleuder
class ListsController < BaseController
def find_all
current_account.scoped(List)
end
def create(listname, fingerprint, adminaddress, adminfingerprint, adminkey)
authorized?(List, :create)
ListBuilder.new(
{email: listname, fingerprint: fingerprint}, adminaddress, adminfingerprint, adminkey
).run
end
def new_list
List.new
end
def find(identifier)
list = get_list_by_id_or_email(identifier)
authorized?(list, :read)
list
end
def update(identifier, attributes)
list = get_list_by_id_or_email(identifier)
authorized?(list, :update)
list.update(attributes)
end
def delete(identifier)
list = get_list_by_id_or_email(identifier)
authorized?(list, :delete)
list.destroy
end
def get_configurable_attributes
List.configurable_attributes
end
def send_list_key_to_subscriptions(list_id)
list = get_list_by_id_or_email(list_id)
authorized?(list, :send_list_key)
list.send_list_key_to_subscriptions
end
end
end
module Schleuder
class SubscriptionsController < BaseController
def find_all(filter={})
authorized?(Subscription, :list)
current_account.scoped(Subscription).where(filter)
end
def find(id)
subscription = Subscription.where(id: id).first
authorized?(subscription, :read)
subscription
end
def subscribe(list_id, attributes, key_material)
list = get_list_by_id_or_email(list_id)
authorized?(list, :subscribe)
list.subscribe(
attributes['email'],
attributes['fingerprint'],
attributes['admin'],
attributes['delivery_enabled'],
key_material
)
end
def update(id, attributes)
subscription = Subscription.where(id: id).first
authorized?(subscription, :update)
subscription.update(attributes)
end
def delete(id)
subscription = Subscription.where(id: id).first
authorized?(subscription, :delete)
subscription.destroy
end
def get_configurable_attributes
Subscription.configurable_attributes
end
def new_subscription
Subscription.new
end
end
end
module Schleuder
module Errors
class Unauthorized < Base
def initialize
super t('errors.unauthorized')
end
end
end
end
......@@ -122,4 +122,117 @@ describe 'lists via api' do
expect(JSON.parse(last_response.body)['email']).to eq list.email
end
context 'configurable_attributes' do
it 'returns the configurable_attributes of the list model' do
authorize_as_api_superadmin!
get 'lists/configurable_attributes.json'
expect(last_response.status).to be 200
expect(JSON.parse(last_response.body)).to eq([
'bounces_drop_all', 'bounces_drop_on_headers', 'bounces_notify_admins',
'forward_all_incoming_to_admins', 'headers_to_meta', 'include_list_headers',
'include_openpgp_header', 'internal_footer', 'keep_msgid', 'keywords_admin_notify', 'keywords_admin_only',
'language', 'log_level', 'logfiles_to_keep', 'max_message_size_kb', 'openpgp_header_preference',
'public_footer', 'receive_admin_only', 'receive_authenticated_only', 'receive_encrypted_only',
'receive_from_subscribed_emailaddresses_only', 'receive_signed_only', 'send_encrypted_only',
'subject_prefix', 'subject_prefix_in', 'subject_prefix_out',
])
end
end
context 'send_list_key_to_subscriptions' do
it 'raises an error if the list id param is missing' do
authorize_as_api_superadmin!
_list = create(:list)
post 'lists/send_list_key_to_subscriptions.json'
expect(last_response.status).to be 400
expect(JSON.parse(last_response.body)['errors']).to eq ('Need "list_id" query-parameter')
end
it 'returns true if list param is present and user is authorized' do
authorize_as_api_superadmin!
list = create(:list)
parameters = { list_id: list.id }
post "lists/send_list_key_to_subscriptions.json?list_id=#{list.id}", { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 200
expect(JSON.parse(last_response.body)['result']).to eq true
end
it 'returns not authorized when user is not authorized' do
list = create(:list)
subscription = create(:subscription, list_id: list.id, admin: false)
account = create(:account, email: subscription.email)
authorize!(account.email, account.set_new_password!)
parameters = { list_id: list.id }
post "lists/send_list_key_to_subscriptions.json?list_id=#{list.id}", parameters.to_json, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 403
expect(last_response.body).to eql('Not authorized')
end
end
context 'new list' do
it 'returns a new list' do
authorize_as_api_superadmin!
get 'lists/new.json'
expect(last_response.status).to be 200
expect(last_response.body).to eq List.new().to_json
end
end
context 'update' do
it 'returns 403 if list was updated successfully' do
authorize_as_api_superadmin!
list = create(:list)
parameters = { 'email' => 'new_address@example.org' }
put "lists/#{list.id}.json", parameters.to_json, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to eq 204
end
it 'returns not authorized when user is not authorized' do
list = create(:list)
subscription = create(:subscription, list_id: list.id, admin: false)
unauthorized_account = create(:account, email: subscription.email)
authorize!(unauthorized_account.email, unauthorized_account.set_new_password!)
parameters = { 'email' => 'new_address@example.org' }
put "lists/#{list.id}.json", parameters.to_json, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 403
expect(last_response.body).to eql('Not authorized')
end
end
context 'delete' do
it 'returns 200 if list was deleted successfully' do
authorize_as_api_superadmin!
list = create(:list)
delete "lists/#{list.id}.json"
expect(last_response.status).to eq 200
end
it 'returns not authorized when user is not authorized' do
list = create(:list)
subscription = create(:subscription, list_id: list.id, admin: false)
unauthorized_account = create(:account, email: subscription.email)
authorize!(unauthorized_account.email, unauthorized_account.set_new_password!)
delete "lists/#{list.id}.json"
expect(last_response.status).to be 403
expect(last_response.body).to eql('Not authorized')
end
end
end
......@@ -8,9 +8,8 @@ describe 'subscription via api' do
_other_subscription = create(:subscription)
account = create(:account, email: subscription.email)
authorize!(account.email, account.set_new_password!)
parameters = { list_id: 'somelist@example.org' }
get 'subscriptions.json', parameters
get "subscriptions.json?list_id=#{list.email}", { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 200
expect(JSON.parse(last_response.body).length).to be 1
......@@ -23,39 +22,35 @@ describe 'subscription via api' do
_other_subscription = create(:subscription)
account = create(:account, email: subscription.email)
authorize!(account.email, account.set_new_password!)
parameters = { fingerprint: subscription.fingerprint }
get 'subscriptions.json', parameters
get "subscriptions.json?fingerprint=#{subscription.fingerprint}", { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 200
expect(JSON.parse(last_response.body).length).to be 1
expect(JSON.parse(last_response.body)[0]['email']).to eq subscription.email
end
it 'returns a 404 when no list with the given email exists' do
it 'returns a 403 when no list with the given email exists' do
list = create(:list, email: 'somelist@example.org')
subscription = create(:subscription, list_id: list.id, admin: true)
_other_subscription = create(:subscription)
account = create(:account, email: subscription.email)
authorize!(account.email, account.set_new_password!)
parameters = { list_id: 'non_existing@example.org' }
get 'subscriptions.json', parameters
get 'subscriptions.json?list_id=non_existing@example.org', { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 404
expect(last_response.body).to eq 'Not found'
expect(last_response.status).to be 403
expect(last_response.body).to eq 'Not authorized'
end
it 'returns an empty array if the no subscription is associated with the account' do
it 'returns a 403 if no subscription is associated with the account' do
list = create(:list)
account = create(:account)
authorize!(account.email, account.set_new_password!)
parameters = { list_id: list.email }
get 'subscriptions.json', parameters
get "subscriptions.json?list_id=#{list.email}", { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to eq 200
expect(last_response.body).to eq [].to_json
expect(last_response.status).to eq 403
expect(last_response.body).to eq 'Not authorized'
end
end
......@@ -235,4 +230,77 @@ describe 'subscription via api' do
expect(last_response.status).to be 403
expect(list.reload.subscriptions.map(&:email)).to include(subscription.email)
end
context 'configurable attributes' do
it 'retuns the configurable_attributes' do
authorize_as_api_superadmin!
get '/subscriptions/configurable_attributes.json'
expect(last_response.status).to be 200
expect(JSON.parse(last_response.body)).to eq ['fingerprint', 'admin', 'delivery_enabled']
end
end
context 'new' do
it 'returns a new subscription' do
authorize_as_api_superadmin!
get 'subscriptions/new.json'
expect(last_response.status).to be 200
expect(last_response.body).to eq Subscription.new().to_json
end
end
context 'getting a subscription' do
it 'returns a subscription for a given identifier' do
list = create(:list)
subscription = create(:subscription, list_id: list.id, admin: true)
account = create(:account, email: subscription.email)
authorize!(account.email, account.set_new_password!)
get "/subscriptions/#{subscription.id}.json"
expect(last_response.status).to be 200
expect(JSON.parse(last_response.body)['email']).to eq subscription.email
end
it 'raises unauthorized if the account is not associated with the list' do
list = create(:list)
subscription = create(:subscription, list_id: list.id)
account = create(:account, email: 'foo@example.org')
authorize!(account.email, account.set_new_password!)
get "/subscriptions/#{subscription.id}.json"
expect(last_response.status).to be 403
end
end
context 'updating a subscription' do
it 'returns 200 if subscription was updated successfully' do
list = create(:list)
subscription = create(:subscription, list_id: list.id, admin: true)
account = create(:account, email: subscription.email)
authorize!(account.email, account.set_new_password!)
parameters = { list_id: list.id, email: 'new@example.org' }
patch "/subscriptions/#{subscription.id}.json", parameters.to_json, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response.status).to be 200
end
it 'raises unauthorized if the account is not associated with the list' do
list = create(:list)
subscription = create(:subscription, list_id: list.id)
account = create(:account, email: 'foo@example.org')