Compare commits

...

31 Commits

Author SHA1 Message Date
92b3b14107
Remove obsolete script
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-05 17:43:41 +01:00
5efab8b733
Update README
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-05 17:38:55 +01:00
2772463196
Update Gemfile 2024-03-05 17:38:35 +01:00
f0d6e6c2c4
Add TODO note 2024-03-05 16:59:37 +01:00
8bcf3854c2
Remove obsolete method 2024-03-05 16:58:36 +01:00
771ff97cfe
Improve argument name
It's used in the code for escaping only parts of URLs
2024-03-05 16:57:15 +01:00
3512c0f7c9 Merge pull request 'Fix invalid PUT signatures for URI-encoded paths' (#9) from bugfix/8-signatures into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #9
2024-03-05 15:55:50 +00:00
aff383b99b Merge pull request 'Set up Drone CI, remove Actions configs' (#11) from dev/actions into bugfix/8-signatures
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #11
2024-03-05 13:30:03 +00:00
6455f24ab0
Set up Drone CI, remove Actions configs
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-05 14:26:49 +01:00
bf14785404
Fix invalid PUT signatures for URI-encoded paths
Some checks failed
Tests / test (6, 2.7) (pull_request) Failing after 19s
Tests / test (6, 3.0) (pull_request) Failing after 3s
Tests / test (6, 3.1) (pull_request) Failing after 3s
Tests / test (7, 2.7) (pull_request) Failing after 3s
Tests / test (7, 3.0) (pull_request) Failing after 3s
Tests / test (7, 3.1) (pull_request) Failing after 3s
We encode the URLs ourselves, and the default option for the aws-sigv4
gem breaks that functionality.
2024-03-04 17:17:44 +01:00
8b6f201a0b Merge pull request 'Switch to AWS v4 signatures' (#5) from feature/aws_v4_signatures into master
Some checks reported warnings
Tests / test (6, 2.7) (push) Has been cancelled
Tests / test (6, 3.0) (push) Has been cancelled
Tests / test (6, 3.1) (push) Has been cancelled
Tests / test (7, 2.7) (push) Has been cancelled
Tests / test (7, 3.0) (push) Has been cancelled
Tests / test (7, 3.1) (push) Has been cancelled
Reviewed-on: #5
2023-11-21 14:19:38 +00:00
209fcca5ea
Make headers argument optional 2023-11-21 15:18:13 +01:00
589dcf7fa2
Update example config 2023-11-16 12:39:46 +01:00
91cadbf228
Switch Docker image back to Ruby 3.1 2023-11-16 11:45:07 +01:00
d09c6e7a39
Use production env for Docker image 2023-11-16 11:41:57 +01:00
93fd1ecd5f Merge pull request 'Add Dockerfile' (#2) from dev/add_dockerfile into master
Reviewed-on: #2
2023-11-16 10:40:51 +00:00
024f516a9d
Expose Content-Type header 2023-11-16 11:39:33 +01:00
96417e3884
Use development env and Webrick in dev run script 2023-11-16 11:27:15 +01:00
b7ff3e7d42
Remove debug output, rescue 2023-11-16 10:27:58 +01:00
02820203f0
Switch to AWS V4 signatures 2023-11-15 19:05:21 +01:00
189c304a08
Add example script for running server in dev 2023-11-15 19:03:33 +01:00
273768ea69
Add Dockerfile 2023-11-01 16:21:34 +01:00
648c3943e8
Add redis db config 2023-10-26 17:31:52 +02:00
9aed3f7cca
Update README 2023-10-26 16:25:07 +02:00
044bf1c3eb
Remove purdytest, update Minitest class name 2023-10-26 16:17:52 +02:00
53eb5461fe
Add ENV vars to config file, switch to ERB template 2023-10-26 16:17:49 +02:00
e90ed70002
Remove Webrick method in favor of stdlib 2023-10-26 15:58:04 +02:00
5b8c1f1e9d
Update tested Redis versions 2023-10-26 15:57:23 +02:00
49614544db
Update dependencies
Requires Ruby 2.7+
2023-10-26 15:54:22 +02:00
285073f7a7
Merge pull request #151 from 5apps/dependabot/bundler/rack-2.2.3.1
Bump rack from 2.2.3 to 2.2.3.1
2022-05-27 19:52:49 +02:00
dependabot[bot]
7fc86fe9ae
Bump rack from 2.2.3 to 2.2.3.1
Bumps [rack](https://github.com/rack/rack) from 2.2.3 to 2.2.3.1.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/2.2.3...2.2.3.1)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-27 16:42:50 +00:00
14 changed files with 192 additions and 189 deletions

16
.drone.yml Normal file
View File

@ -0,0 +1,16 @@
kind: pipeline
name: default
steps:
- name: specs
image: ruby
environment:
REDIS_HOST: redis
commands:
- cp config.yml.erb.example config.yml.erb
- bundle install --jobs=3 --retry=3
- bundle exec rake test
services:
- name: redis
image: redis

View File

@ -1,35 +0,0 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.5', '2.6', '2.7']
redis-version: [4, 5, 6]
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Start Redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: ${{ matrix.redis-version }}
- name: Configure
run: cp config.yml.example.s3 config.yml
- name: Run tests
run: bundle exec rake test

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
config.yml config.yml
config.yml.erb
cs_credentials.json cs_credentials.json
pids pids
.bundle .bundle

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM ruby:3.1.4
WORKDIR /liquorcabinet
ENV RACK_ENV=production
COPY Gemfile Gemfile.lock /liquorcabinet/
RUN bundle install
COPY . /liquorcabinet
COPY ./config.yml.erb.example /liquorcabinet/config.yml.erb
EXPOSE 4567
CMD ["bundle", "exec", "rainbows", "--listen", "0.0.0.0:4567"]

11
Gemfile
View File

@ -2,23 +2,22 @@ source "https://rubygems.org"
gem "sinatra", "~> 2.2.0" gem "sinatra", "~> 2.2.0"
gem "sinatra-contrib", "~> 2.2.0" gem "sinatra-contrib", "~> 2.2.0"
gem "activesupport", "~> 6.0.5" gem "activesupport", "~> 6.1.0"
gem "rest-client", "~> 2.1.0" # Fixes a memory leak in Ruby 2.4
gem "redis", "~> 4.6.0" gem "redis", "~> 4.6.0"
# Remove require when we can update to 3.0, which sets the new storage gem "rest-client", "~> 2.1.0"
# format to columnar by default. Increases performance gem "aws-sigv4", "~> 1.0.0"
gem "mime-types" gem "mime-types"
gem "rainbows"
group :test do group :test do
gem 'rake' gem 'rake'
gem 'rack-test' gem 'rack-test'
gem 'purdytest', :require => false
gem 'm' gem 'm'
gem 'minitest'
gem 'minitest-stub_any_instance' gem 'minitest-stub_any_instance'
gem 'webmock' gem 'webmock'
end end
group :staging, :production do group :staging, :production do
gem "rainbows"
gem "sentry-raven", require: false gem "sentry-raven", require: false
end end

View File

@ -1,102 +1,104 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (6.0.5) activesupport (6.1.7.6)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (>= 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
zeitwerk (~> 2.2, >= 2.2.2) zeitwerk (~> 2.3)
addressable (2.8.0) addressable (2.8.5)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 6.0)
concurrent-ruby (1.1.10) aws-sigv4 (1.0.3)
crack (0.4.3) base64 (0.1.1)
safe_yaml (~> 1.0.0) concurrent-ruby (1.2.2)
crack (0.4.5)
rexml
domain_name (0.5.20190701) domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
faraday (0.17.0) faraday (2.7.11)
multipart-post (>= 1.2, < 3) base64
hashdiff (1.0.0) faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
hashdiff (1.0.1)
http-accept (1.7.0) http-accept (1.7.0)
http-cookie (1.0.3) http-cookie (1.0.5)
domain_name (~> 0.5) domain_name (~> 0.5)
i18n (1.10.0) i18n (1.14.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
kgio (2.11.2) kgio (2.11.4)
m (1.5.1) m (1.6.2)
method_source (>= 0.6.7) method_source (>= 0.6.7)
rake (>= 0.9.2.2) rake (>= 0.9.2.2)
method_source (0.9.2) method_source (1.0.0)
mime-types (3.3) mime-types (3.5.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009) mime-types-data (3.2023.1003)
minitest (5.13.0) minitest (5.20.0)
minitest-stub_any_instance (1.0.2) minitest-stub_any_instance (1.0.3)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.1.1) mustermann (2.0.2)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1) ruby2_keywords (~> 0.0.1)
netrc (0.11.0) netrc (0.11.0)
public_suffix (4.0.1) public_suffix (5.0.3)
purdytest (2.0.0) rack (2.2.8)
minitest (~> 5.5) rack-protection (2.2.4)
rack (2.2.3)
rack-protection (2.2.0)
rack rack
rack-test (1.1.0) rack-test (2.1.0)
rack (>= 1.0, < 3) rack (>= 1.3)
rainbows (5.2.0) rainbows (5.2.1)
kgio (~> 2.5) kgio (~> 2.5)
rack (>= 1.1, < 3.0) rack (>= 1.1, < 3.0)
unicorn (~> 5.1) unicorn (~> 5.1)
raindrops (0.19.0) raindrops (0.20.1)
rake (13.0.1) rake (13.0.6)
redis (4.6.0) redis (4.6.0)
rest-client (2.1.0) rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0) http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
rexml (3.2.6)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
safe_yaml (1.0.5) sentry-raven (3.1.2)
sentry-raven (2.12.3) faraday (>= 1.0)
faraday (>= 0.7.6, < 1.0) sinatra (2.2.4)
sinatra (2.2.0) mustermann (~> 2.0)
mustermann (~> 1.0)
rack (~> 2.2) rack (~> 2.2)
rack-protection (= 2.2.0) rack-protection (= 2.2.4)
tilt (~> 2.0) tilt (~> 2.0)
sinatra-contrib (2.2.0) sinatra-contrib (2.2.4)
multi_json multi_json
mustermann (~> 1.0) mustermann (~> 2.0)
rack-protection (= 2.2.0) rack-protection (= 2.2.4)
sinatra (= 2.2.0) sinatra (= 2.2.4)
tilt (~> 2.0) tilt (~> 2.0)
thread_safe (0.3.6) tilt (2.3.0)
tilt (2.0.10) tzinfo (2.0.6)
tzinfo (1.2.9) concurrent-ruby (~> 1.0)
thread_safe (~> 0.1)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.6) unf_ext (0.0.8.2)
unicorn (5.5.1) unicorn (5.8.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
webmock (3.7.6) webmock (3.19.1)
addressable (>= 2.3.6) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
zeitwerk (2.5.4) zeitwerk (2.6.12)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
activesupport (~> 6.0.5) activesupport (~> 6.1.0)
aws-sigv4 (~> 1.0.0)
m m
mime-types mime-types
minitest
minitest-stub_any_instance minitest-stub_any_instance
purdytest
rack-test rack-test
rainbows rainbows
rake rake
@ -108,4 +110,4 @@ DEPENDENCIES
webmock webmock
BUNDLED WITH BUNDLED WITH
1.16.0 2.3.7

View File

@ -1,21 +1,54 @@
[![Build Status](https://github.com/5apps/liquor-cabinet/actions/workflows/ruby.yml/badge.svg)](https://github.com/5apps/liquor-cabinet/actions/workflows/ruby.yml) [![Build Status](https://drone.kosmos.org/api/badges/5apps/liquor-cabinet/status.svg)](https://drone.kosmos.org/5apps/liquor-cabinet)
# Liquor Cabinet # Liquor Cabinet
Liquor Cabinet is where Frank stores all his stuff. It's a Liquor Cabinet is where Frank stores all his stuff. It's a
[remoteStorage](https://remotestorage.io) HTTP API, based on Sinatra. The [remoteStorage](https://remotestorage.io) HTTP API, based on Sinatra. The
metadata and OAuth tokens are stored in Redis, and documents can be stored in metadata and OAuth tokens are stored in Redis, and
anything that supports the storage API of either Openstack Swift or Amazon S3. documents/files can be stored in anything that supports
the S3 object storage API.
Liquor Cabinet only implements the storage API part of the remoteStorage Liquor Cabinet only implements the storage API part of the remoteStorage
protocol, but does not include the Webfinger and OAuth parts. It is meant to be protocol, but does not include the Webfinger and OAuth parts. It is meant to be
added to existing systems and user accounts, so you will have to add your own added to existing systems and user accounts, so you will have to add your own
OAuth dialog for remoteStorage authorizations and persist the tokens in Redis. OAuth dialog for remoteStorage authorizations and persist the tokens in Redis.
If you have any questions about this program, drop by #remotestorage on There is an [open-source accounts management
Freenode, or [post to the RS app](https://gitea.kosmos.org/kosmos/akkounts/) by the Kosmos project, which
comes with a built-in remoteStorage dashboard and is compatible with Liquor
Cabinet.
If you have any questions about this program, please [post to the RS
forums](https://community.remotestorage.io/c/server-development), and we'll forums](https://community.remotestorage.io/c/server-development), and we'll
happily answer them. gladly answer them.
## System requirements
* [Ruby](https://www.ruby-lang.org/en/) and [Bundler](https://bundler.io/)
* [Redis](https://redis.io/)
* S3-compatible object storage (e.g. [Garage](https://garagehq.deuxfleurs.fr/)
or [MinIO](https://min.io/) for self-hosting)
## Setup
1. Check the `config.yml.erb.example` file. Either copy it to `config.yml.erb`
and use the enviroment variables it contains, or create/deploy your own
config YAML file with custom values.
2. Install dependencies: `bundle install`
## Development
Running the test suite:
bundle exec rake test
Running the app:
bundle exec rainbows
## Deployment
_TODO document options_
## Contributing ## Contributing
@ -23,5 +56,5 @@ We love pull requests. If you want to submit a patch:
* Fork the project. * Fork the project.
* Make your feature addition or bug fix. * Make your feature addition or bug fix.
* Write specs for it. This is important so nobody breaks it in a future version unintentionally. * Write specs for it. This is important so nobody breaks it in a future version.
* Push to your fork and send a pull request. * Push to your fork and send a pull request.

18
config.yml.erb.example Normal file
View File

@ -0,0 +1,18 @@
development: &defaults
maintenance: false
redis:
host: <%= ENV["REDIS_HOST"] || "localhost" %>
port: <%= ENV["REDIS_PORT"] || 6379 %>
db: <%= ENV["REDIS_DB"] || 1 %>
s3: &s3_defaults
endpoint: <%= ENV["S3_ENDPOINT"] || "http://127.0.0.1:9000" %>
region: <%= ENV["S3_REGION"] || "us-east-1" %>
access_key_id: <%= ENV["S3_ACCESS_KEY"] || "minioadmin" %>
secret_key_id: <%= ENV["S3_SECRET_KEY"] || "minioadmin" %>
bucket: <%= ENV["S3_BUCKET"] || "rs-development" %>
test:
<<: *defaults
staging:
<<: *defaults
production:
<<: *defaults

View File

@ -1,26 +0,0 @@
development: &defaults
maintenance: false
s3:
endpoint: "https://some-endpoint"
region: "region"
access_key_id: ""
secret_key_id: ""
bucket: "test-bucket"
redis:
host: localhost
port: 6379
test:
<<: *defaults
s3:
endpoint: "https://some-endpoint"
region: "region"
access_key_id: ""
secret_key_id: ""
bucket: "test-bucket"
staging:
<<: *defaults
production:
<<: *defaults

View File

@ -431,9 +431,9 @@ module RemoteStorage
end end
end end
def escape(url) def escape(str)
# We want spaces to turn into %20 and slashes to stay slashes # We want spaces to turn into %20 and slashes to stay slashes
CGI::escape(url).gsub('+', '%20').gsub('%2F', '/') CGI::escape(str).gsub('+', '%20').gsub('%2F', '/')
end end
def redis def redis

View File

@ -1,8 +1,8 @@
require "remote_storage/rest_provider" require "remote_storage/rest_provider"
require "digest" require "digest"
require "base64" require "base64"
require "cgi"
require "openssl" require "openssl"
require "webrick/httputils"
module RemoteStorage module RemoteStorage
class S3 class S3
@ -10,18 +10,29 @@ module RemoteStorage
private private
# S3 already wraps the ETag around quotes def s3_signer
signer ||= Aws::Sigv4::Signer.new(
service: 's3',
region: settings.s3["region"],
access_key_id: settings.s3["access_key_id"].to_s,
secret_access_key: settings.s3["secret_key_id"].to_s,
uri_escape_path: false
)
end
# S3 already wraps the ETag with quotes
def format_etag(etag) def format_etag(etag)
etag etag
end end
def do_put_request(url, data, content_type) def do_put_request(url, data, content_type)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
md5 = Digest::MD5.base64digest(data) headers = { "Content-Type" => content_type }
authorization_headers = authorization_headers_for( auth_headers = auth_headers_for("PUT", url, headers, data)
"PUT", url, md5, content_type
).merge({ "Content-Type" => content_type, "Content-Md5" => md5 }) # TODO check if put was successful, e.g. it's returning a 413 directly
res = RestClient.put(url, data, authorization_headers) # if the back-end does, too (missing CORS headers in that case)
res = RestClient.put(url, data, headers.merge(auth_headers))
return [ return [
res.headers[:etag].delete('"'), res.headers[:etag].delete('"'),
@ -32,24 +43,24 @@ module RemoteStorage
def do_get_request(url, &block) def do_get_request(url, &block)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
headers = { } headers = {}
headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"] headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"]
authorization_headers = authorization_headers_for("GET", url) auth_headers = auth_headers_for("GET", url, headers)
RestClient.get(url, authorization_headers.merge(headers), &block) RestClient.get(url, headers.merge(auth_headers), &block)
end end
end end
def do_head_request(url, &block) def do_head_request(url, &block)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
authorization_headers = authorization_headers_for("HEAD", url) auth_headers = auth_headers_for("HEAD", url)
RestClient.head(url, authorization_headers, &block) RestClient.head(url, auth_headers, &block)
end end
end end
def do_delete_request(url) def do_delete_request(url)
deal_with_unauthorized_requests do deal_with_unauthorized_requests do
authorization_headers = authorization_headers_for("DELETE", url) auth_headers = auth_headers_for("DELETE", url)
RestClient.delete(url, authorization_headers) RestClient.delete(url, auth_headers)
end end
end end
@ -67,38 +78,11 @@ module RemoteStorage
return found return found
end end
# This is using the S3 authorizations, not the newer AW V4 Signatures def auth_headers_for(http_method, url, headers = {}, data = nil)
# (https://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html) signature = s3_signer.sign_request(
def authorization_headers_for(http_verb, url, md5 = nil, content_type = nil) http_method: http_method, url: url, headers: headers, body: data
url = File.join("/", url.gsub(base_url, "")) )
date = Time.now.httpdate signature.headers
signed_data = generate_s3_signature(http_verb, md5, content_type, date, url)
{
"Authorization" => "AWS #{credentials[:access_key_id]}:#{signed_data}",
"Date" => date
}
end
def credentials
@credentials ||= { access_key_id: settings.s3["access_key_id"], secret_key_id: settings.s3["secret_key_id"] }
end
def digest(secret, string_to_sign)
Base64.encode64(hmac(secret, string_to_sign)).strip
end
def hmac(key, value)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), key, value)
end
def uri_escape(s)
WEBrick::HTTPUtils.escape(s).gsub('%5B', '[').gsub('%5D', ']')
end
def generate_s3_signature(http_verb, md5, content_type, date, url)
string_to_sign = [http_verb, md5, content_type, date, url].join "\n"
signature = digest(credentials[:secret_key_id], string_to_sign)
uri_escape(signature)
end end
def base_url def base_url
@ -106,8 +90,7 @@ module RemoteStorage
end end
def container_url_for(user) def container_url_for(user)
"#{base_url}#{settings.s3["bucket"]}/#{user}" "#{base_url}/#{settings.s3["bucket"]}/#{user}"
end end
end end
end end

View File

@ -18,7 +18,7 @@ class LiquorCabinet < Sinatra::Base
register Sinatra::ConfigFile register Sinatra::ConfigFile
set :environments, %w{development test production staging} set :environments, %w{development test production staging}
config_file 'config.yml' config_file 'config.yml.erb'
end end
configure :development do configure :development do
@ -64,7 +64,7 @@ class LiquorCabinet < Sinatra::Base
headers 'Access-Control-Allow-Origin' => '*', headers 'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, PUT, DELETE', 'Access-Control-Allow-Methods' => 'GET, PUT, DELETE',
'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match, Range', 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, Origin, If-Match, If-None-Match, Range',
'Access-Control-Expose-Headers' => 'ETag, Content-Length, Content-Range', 'Access-Control-Expose-Headers' => 'ETag, Content-Length, Content-Range, Content-Type',
'Accept-Ranges' => 'bytes' 'Accept-Ranges' => 'bytes'
headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"] headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"]
headers['Cache-Control'] = 'no-cache' headers['Cache-Control'] = 'no-cache'

View File

@ -2,7 +2,7 @@ require_relative "../spec_helper"
describe "S3 provider" do describe "S3 provider" do
def container_url_for(user) def container_url_for(user)
"#{app.settings.s3["endpoint"]}#{app.settings.s3["bucket"]}/#{user}" "#{app.settings.s3["endpoint"]}/#{app.settings.s3["bucket"]}/#{user}"
end end
def storage_class def storage_class

View File

@ -8,7 +8,6 @@ require_relative '../liquor-cabinet'
require 'minitest/autorun' require 'minitest/autorun'
require "minitest/stub_any_instance" require "minitest/stub_any_instance"
require 'rack/test' require 'rack/test'
require 'purdytest'
require "redis" require "redis"
require "rest_client" require "rest_client"
require "ostruct" require "ostruct"
@ -36,22 +35,22 @@ if app.settings.respond_to? :redis
end end
end end
MiniTest::Spec.class_eval do Minitest::Spec.class_eval do
def self.shared_examples def self.shared_examples
@shared_examples ||= {} @shared_examples ||= {}
end end
end end
module MiniTest::Spec::SharedExamples module Minitest::Spec::SharedExamples
def shared_examples_for(desc, &block) def shared_examples_for(desc, &block)
MiniTest::Spec.shared_examples[desc] = block Minitest::Spec.shared_examples[desc] = block
end end
def it_behaves_like(desc) def it_behaves_like(desc)
self.instance_eval(&MiniTest::Spec.shared_examples[desc]) self.instance_eval(&Minitest::Spec.shared_examples[desc])
end end
end end
Object.class_eval { include(MiniTest::Spec::SharedExamples) } Object.class_eval { include(Minitest::Spec::SharedExamples) }
require_relative 'shared_examples' require_relative 'shared_examples'