From 0d15277178b520da2487f39239bb1964ec2ff934 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Mon, 27 Feb 2012 16:32:12 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Gemfile | 9 +++ Gemfile.lock | 43 ++++++++++++ README.md | 13 ++++ Rakefile | 5 ++ config.ru | 7 ++ config.yml.example | 11 +++ lib/remote_storage/riak.rb | 43 ++++++++++++ liquor-cabinet.gemspec | 26 +++++++ liquor-cabinet.rb | 51 ++++++++++++++ spec/app_spec.rb | 137 +++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 15 ++++ 12 files changed, 361 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 Rakefile create mode 100644 config.ru create mode 100644 config.yml.example create mode 100644 lib/remote_storage/riak.rb create mode 100644 liquor-cabinet.gemspec create mode 100644 liquor-cabinet.rb create mode 100644 spec/app_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d3ed4c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.yml diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..38741b7 --- /dev/null +++ b/Gemfile @@ -0,0 +1,9 @@ +source "http://rubygems.org" + +gem "sinatra" +gem "sinatra-contrib" +gem "riak-client" + +group :test do + gem 'purdytest', :require => false +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..25fc62a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,43 @@ +GEM + remote: http://rubygems.org/ + specs: + backports (2.3.0) + beefcake (0.3.7) + builder (3.0.0) + eventmachine (0.12.10) + i18n (0.6.0) + minitest (2.10.0) + multi_json (1.0.4) + purdytest (1.0.0) + minitest (~> 2.2) + rack (1.3.5) + rack-protection (1.1.4) + rack + rack-test (0.6.1) + rack (>= 1.0) + riak-client (1.0.0) + beefcake (~> 0.3.7) + builder (>= 2.1.2) + i18n (>= 0.4.0) + multi_json (~> 1.0.0) + sinatra (1.3.1) + rack (>= 1.3.4, ~> 1.3) + rack-protection (>= 1.1.2, ~> 1.1) + tilt (>= 1.3.3, ~> 1.3) + sinatra-contrib (1.3.1) + backports (>= 2.0) + eventmachine + rack-protection + rack-test + sinatra (~> 1.3.0) + tilt (~> 1.3) + tilt (1.3.3) + +PLATFORMS + ruby + +DEPENDENCIES + purdytest + riak-client + sinatra + sinatra-contrib diff --git a/README.md b/README.md new file mode 100644 index 0000000..8507315 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Liquor Cabinet + +Liquor Cabinet is where Frank stores all his stuff. It's a +remoteStorage-compatible storage provider API, based on Sinatra and currently +using Riak as backend. You can use it on its own, or e.g. mount it from a Rails +application. + +It's merely implementing the storage API, not including the Webfinger and Oauth +parts of remoteStorage. You have to set the authorization keys/values in the +database yourself. + +If you have any questions about this thing, drop by #unhosted on Freenode, and +we'll happily answer them. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..9618286 --- /dev/null +++ b/Rakefile @@ -0,0 +1,5 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.pattern = 'spec/**/*_spec.rb' +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..19bd439 --- /dev/null +++ b/config.ru @@ -0,0 +1,7 @@ +require 'rubygems' +require 'bundler' + +Bundler.require + +require './liquor_cabinet' +run LiquorCabinet diff --git a/config.yml.example b/config.yml.example new file mode 100644 index 0000000..6e90101 --- /dev/null +++ b/config.yml.example @@ -0,0 +1,11 @@ +development: + riak: + host: localhost + http_port: 8098 + +test: + riak: + host: localhost + http_port: 8098 + +production: diff --git a/lib/remote_storage/riak.rb b/lib/remote_storage/riak.rb new file mode 100644 index 0000000..296236f --- /dev/null +++ b/lib/remote_storage/riak.rb @@ -0,0 +1,43 @@ +require "riak" + +module RemoteStorage + module Riak + + def authorize_request(user, category, token) + return true if category == "public" && env["REQUEST_METHOD"] == "GET" + + client = ::Riak::Client.new(settings.riak_config) + categories = client.bucket("authorizations").get("#{user}:#{token}").data + + halt 403 unless categories.include?(category) + rescue ::Riak::HTTPFailedRequest + halt 403 + end + + def get_data(user, category, key) + client = ::Riak::Client.new(settings.riak_config) + client.bucket("user_data").get("#{user}:#{category}:#{key}").data + rescue ::Riak::HTTPFailedRequest + halt 404 + end + + def put_data(user, category, key, data) + client = ::Riak::Client.new(settings.riak_config) + object = client.bucket("user_data").new("#{user}:#{category}:#{key}") + object.content_type = "text/plain" + object.data = data + object.store + rescue ::Riak::HTTPFailedRequest + halt 422 + end + + def delete_data(user, category, key) + client = ::Riak::Client.new(settings.riak_config) + riak_response = client.bucket("user_data").delete("#{user}:#{category}:#{key}") + halt riak_response[:code] + rescue ::Riak::HTTPFailedRequest + halt 404 + end + + end +end diff --git a/liquor-cabinet.gemspec b/liquor-cabinet.gemspec new file mode 100644 index 0000000..a48d80c --- /dev/null +++ b/liquor-cabinet.gemspec @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +lib = File.expand_path('../lib/', __FILE__) +$:.unshift lib unless $:.include?(lib) + +require 'bundler/version' + +Gem::Specification.new do |s| + s.name = "liquor-cabinet" + s.version = "0.0.1" + s.platform = Gem::Platform::RUBY + s.authors = ["Sebastian Kippe"] + s.email = ["sebastian@5apps.com"] + s.homepage = "" + s.summary = "" + s.description = "" + + s.required_rubygems_version = ">= 1.3.6" + + s.add_dependency('sinatra') + s.add_dependency('sinatra-contrib') + s.add_dependency('riak-client') + + s.files = Dir.glob("{bin,lib}/**/*") + Dir['*.rb'] + # s.executables = ['config.ru'] + s.require_paths << '.' +end diff --git a/liquor-cabinet.rb b/liquor-cabinet.rb new file mode 100644 index 0000000..f5124ca --- /dev/null +++ b/liquor-cabinet.rb @@ -0,0 +1,51 @@ +$LOAD_PATH << File.join(File.expand_path(File.dirname(__FILE__)), 'lib') + +require "json" +require "sinatra/base" +require "sinatra/reloader" +require "remote_storage/riak" + +class LiquorCabinet < Sinatra::Base + + include RemoteStorage::Riak + + configure :development do + register Sinatra::Reloader + enable :logging + end + + configure :development, :test, :production do + config = File.read(File.expand_path('config.yml', File.dirname(__FILE__))) + riak_config = YAML.load(config)[ENV['RACK_ENV']]['riak'].symbolize_keys + set :riak_config, riak_config + end + + before "/:user/:category/:key" do + @user, @category, @key = params[:user], params[:category], params[:key] + token = env["HTTP_AUTHORIZATION"] ? env["HTTP_AUTHORIZATION"].split(" ")[1] : "" + + authorize_request(@user, @category, token) + end + + get "/ohai" do + "Ohai." + end + + get "/headers" do + env["HTTP_AUTHORIZATION"] + end + + get "/:user/:category/:key" do + get_data(@user, @category, @key) + end + + put "/:user/:category/:key" do + data = request.body.read + put_data(@user, @category, @key, data) + end + + delete "/:user/:category/:key" do + delete_data(@user, @category, @key) + end + +end diff --git a/spec/app_spec.rb b/spec/app_spec.rb new file mode 100644 index 0000000..61d1f6a --- /dev/null +++ b/spec/app_spec.rb @@ -0,0 +1,137 @@ +ENV["RACK_ENV"] = "test" +require_relative "spec_helper" + +describe "App" do + include Rack::Test::Methods + include RemoteStorage::Riak + + def app + LiquorCabinet + end + + def storage_client + ::Riak::Client.new(settings.riak_config) + end + + it "should say hello" do + get "/ohai" + assert last_response.ok? + last_response.body.must_include "Ohai." + end + + it "should return 404 on non-existing routes" do + get "/myunclesam" + last_response.status.must_equal 404 + end + + describe "GET public data" do + before do + object = storage_client.bucket("user_data").new("jimmy:public:foo") + object.content_type = "text/plain" + object.data = "some text data" + object.store + end + + after do + storage_client.bucket("user_data").delete("jimmy:public:foo") + end + + it "returns the value on all get requests" do + get "/jimmy/public/foo" + + last_response.status.must_equal 200 + last_response.body.must_equal "some text data" + end + end + + describe "private data" do + before do + object = storage_client.bucket("user_data").new("jimmy:documents:foo") + object.content_type = "text/plain" + object.data = "some private text data" + object.store + + auth = storage_client.bucket("authorizations").new("jimmy:123") + auth.data = ["documents", "public"] + auth.store + end + + after do + storage_client.bucket("user_data").delete("jimmy:documents:foo") + storage_client.bucket("authorizations").delete("jimmy:123") + end + + describe "GET" do + it "returns the value" do + header "Authorization", "Bearer 123" + get "/jimmy/documents/foo" + + last_response.status.must_equal 200 + last_response.body.must_equal "some private text data" + end + end + + describe "GET nonexisting key" do + it "returns a 404" do + header "Authorization", "Bearer 123" + get "/jimmy/documents/somestupidkey" + + last_response.status.must_equal 404 + end + end + + describe "PUT" do + it "saves the value" do + header "Authorization", "Bearer 123" + put "/jimmy/documents/bar", "another text" + + last_response.status.must_equal 200 + storage_client.bucket("user_data").get("jimmy:documents:bar").data.must_equal "another text" + end + end + + describe "DELETE" do + it "removes the key" do + header "Authorization", "Bearer 123" + delete "/jimmy/documents/foo" + + last_response.status.must_equal 204 + lambda {storage_client.bucket("user_data").get("jimmy:documents:foo")}.must_raise Riak::HTTPFailedRequest + end + end + end + + describe "unauthorized access" do + before do + auth = storage_client.bucket("authorizations").new("jimmy:123") + auth.data = ["documents", "public"] + auth.store + + header "Authorization", "Bearer 321" + end + + describe "GET" do + it "returns a 403" do + get "/jimmy/documents/foo" + + last_response.status.must_equal 403 + end + end + + describe "PUT" do + it "returns a 403" do + put "/jimmy/documents/foo", "some text" + + last_response.status.must_equal 403 + end + end + + describe "DELETE" do + it "returns a 403" do + delete "/jimmy/documents/foo" + + last_response.status.must_equal 403 + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..6a8e504 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,15 @@ +require 'rubygems' +require 'bundler' +Bundler.require + +require_relative '../liquor_cabinet' +require 'minitest/autorun' +require 'rack/test' +require 'purdytest' +require 'riak' + +set :environment, :test + +config = File.read(File.expand_path('../config.yml', File.dirname(__FILE__))) +riak_config = YAML.load(config)[ENV['RACK_ENV']]['riak'].symbolize_keys +set :riak_config, riak_config