Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
ce5f938938 | |||
|
2434c6e0b2 | ||
|
92e9e048a2 | ||
|
62f7d8790a | ||
|
acecb370c6 | ||
|
fbd8e994fb | ||
|
fa8a829ed9 | ||
|
eca4f1e1d7 | ||
|
eb7f39374c | ||
|
29c0d58c2a | ||
|
1b116cf65e | ||
|
28a6d1700b | ||
|
4111cbdddf | ||
|
04197a517e | ||
|
6a3181482d | ||
|
7110f8c315 | ||
|
3fafac0d2d | ||
|
51c1d0937a | ||
|
c3d3e79bd7 | ||
|
396a102755 | ||
|
6347433184 | ||
|
962a908a7a | ||
|
d4b4b619e2 | ||
|
1ce856919e | ||
|
64e6dc7992 | ||
|
031ce9453c | ||
|
9c1e19deb3 | ||
|
c40bb0ebf5 | ||
|
ca80b64111 | ||
|
2a4d148cbb | ||
|
6430a4b605 | ||
|
9dda344b26 | ||
|
207bb0efde | ||
|
3ce3efd0fc |
16
.drone.yml
16
.drone.yml
@ -1,16 +0,0 @@
|
|||||||
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
|
|
35
.github/workflows/ruby.yml
vendored
Normal file
35
.github/workflows/ruby.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
config.yml
|
config.yml
|
||||||
config.yml.erb
|
|
||||||
cs_credentials.json
|
cs_credentials.json
|
||||||
pids
|
pids
|
||||||
.bundle
|
.bundle
|
||||||
|
13
Dockerfile
13
Dockerfile
@ -1,13 +0,0 @@
|
|||||||
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
11
Gemfile
@ -2,22 +2,23 @@ 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.1.0"
|
gem "activesupport", "~> 6.0.5"
|
||||||
|
gem "rest-client", "~> 2.1.0" # Fixes a memory leak in Ruby 2.4
|
||||||
gem "redis", "~> 4.6.0"
|
gem "redis", "~> 4.6.0"
|
||||||
gem "rest-client", "~> 2.1.0"
|
# Remove require when we can update to 3.0, which sets the new storage
|
||||||
gem "aws-sigv4", "~> 1.0.0"
|
# format to columnar by default. Increases performance
|
||||||
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
|
||||||
|
114
Gemfile.lock
114
Gemfile.lock
@ -1,104 +1,102 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (6.1.7.6)
|
activesupport (6.0.5)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 1.1)
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.2, >= 2.2.2)
|
||||||
addressable (2.8.5)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
aws-sigv4 (1.0.3)
|
concurrent-ruby (1.1.10)
|
||||||
base64 (0.1.1)
|
crack (0.4.3)
|
||||||
concurrent-ruby (1.2.2)
|
safe_yaml (~> 1.0.0)
|
||||||
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 (2.7.11)
|
faraday (0.17.0)
|
||||||
base64
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-net_http (>= 2.0, < 3.1)
|
hashdiff (1.0.0)
|
||||||
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.5)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
i18n (1.14.1)
|
i18n (1.10.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
kgio (2.11.4)
|
kgio (2.11.2)
|
||||||
m (1.6.2)
|
m (1.5.1)
|
||||||
method_source (>= 0.6.7)
|
method_source (>= 0.6.7)
|
||||||
rake (>= 0.9.2.2)
|
rake (>= 0.9.2.2)
|
||||||
method_source (1.0.0)
|
method_source (0.9.2)
|
||||||
mime-types (3.5.1)
|
mime-types (3.3)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2023.1003)
|
mime-types-data (3.2019.1009)
|
||||||
minitest (5.20.0)
|
minitest (5.13.0)
|
||||||
minitest-stub_any_instance (1.0.3)
|
minitest-stub_any_instance (1.0.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
mustermann (2.0.2)
|
multipart-post (2.1.1)
|
||||||
|
mustermann (1.1.1)
|
||||||
ruby2_keywords (~> 0.0.1)
|
ruby2_keywords (~> 0.0.1)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
public_suffix (5.0.3)
|
public_suffix (4.0.1)
|
||||||
rack (2.2.8)
|
purdytest (2.0.0)
|
||||||
rack-protection (2.2.4)
|
minitest (~> 5.5)
|
||||||
|
rack (2.2.3)
|
||||||
|
rack-protection (2.2.0)
|
||||||
rack
|
rack
|
||||||
rack-test (2.1.0)
|
rack-test (1.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.0, < 3)
|
||||||
rainbows (5.2.1)
|
rainbows (5.2.0)
|
||||||
kgio (~> 2.5)
|
kgio (~> 2.5)
|
||||||
rack (>= 1.1, < 3.0)
|
rack (>= 1.1, < 3.0)
|
||||||
unicorn (~> 5.1)
|
unicorn (~> 5.1)
|
||||||
raindrops (0.20.1)
|
raindrops (0.19.0)
|
||||||
rake (13.0.6)
|
rake (13.0.1)
|
||||||
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)
|
||||||
sentry-raven (3.1.2)
|
safe_yaml (1.0.5)
|
||||||
faraday (>= 1.0)
|
sentry-raven (2.12.3)
|
||||||
sinatra (2.2.4)
|
faraday (>= 0.7.6, < 1.0)
|
||||||
mustermann (~> 2.0)
|
sinatra (2.2.0)
|
||||||
|
mustermann (~> 1.0)
|
||||||
rack (~> 2.2)
|
rack (~> 2.2)
|
||||||
rack-protection (= 2.2.4)
|
rack-protection (= 2.2.0)
|
||||||
tilt (~> 2.0)
|
tilt (~> 2.0)
|
||||||
sinatra-contrib (2.2.4)
|
sinatra-contrib (2.2.0)
|
||||||
multi_json
|
multi_json
|
||||||
mustermann (~> 2.0)
|
mustermann (~> 1.0)
|
||||||
rack-protection (= 2.2.4)
|
rack-protection (= 2.2.0)
|
||||||
sinatra (= 2.2.4)
|
sinatra (= 2.2.0)
|
||||||
tilt (~> 2.0)
|
tilt (~> 2.0)
|
||||||
tilt (2.3.0)
|
thread_safe (0.3.6)
|
||||||
tzinfo (2.0.6)
|
tilt (2.0.10)
|
||||||
concurrent-ruby (~> 1.0)
|
tzinfo (1.2.9)
|
||||||
|
thread_safe (~> 0.1)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8.2)
|
unf_ext (0.0.7.6)
|
||||||
unicorn (5.8.0)
|
unicorn (5.5.1)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
webmock (3.19.1)
|
webmock (3.7.6)
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
zeitwerk (2.6.12)
|
zeitwerk (2.5.4)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
activesupport (~> 6.1.0)
|
activesupport (~> 6.0.5)
|
||||||
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
|
||||||
@ -110,4 +108,4 @@ DEPENDENCIES
|
|||||||
webmock
|
webmock
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.7
|
1.16.0
|
||||||
|
47
README.md
47
README.md
@ -1,54 +1,21 @@
|
|||||||
[](https://drone.kosmos.org/5apps/liquor-cabinet)
|
[](https://github.com/5apps/liquor-cabinet/actions/workflows/ruby.yml)
|
||||||
|
|
||||||
# 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
|
metadata and OAuth tokens are stored in Redis, and documents can be stored in
|
||||||
documents/files can be stored in anything that supports
|
anything that supports the storage API of either Openstack Swift or Amazon S3.
|
||||||
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.
|
||||||
|
|
||||||
There is an [open-source accounts management
|
If you have any questions about this program, drop by #remotestorage on
|
||||||
app](https://gitea.kosmos.org/kosmos/akkounts/) by the Kosmos project, which
|
Freenode, or [post to the RS
|
||||||
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
|
||||||
gladly answer them.
|
happily 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
|
||||||
|
|
||||||
@ -56,5 +23,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.
|
* Write specs for it. This is important so nobody breaks it in a future version unintentionally.
|
||||||
* Push to your fork and send a pull request.
|
* Push to your fork and send a pull request.
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
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
|
|
26
config.yml.example.s3
Normal file
26
config.yml.example.s3
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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
|
@ -431,9 +431,9 @@ module RemoteStorage
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def escape(str)
|
def escape(url)
|
||||||
# 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(str).gsub('+', '%20').gsub('%2F', '/')
|
CGI::escape(url).gsub('+', '%20').gsub('%2F', '/')
|
||||||
end
|
end
|
||||||
|
|
||||||
def redis
|
def redis
|
||||||
|
@ -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,29 +10,18 @@ module RemoteStorage
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def s3_signer
|
# S3 already wraps the ETag around quotes
|
||||||
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
|
||||||
headers = { "Content-Type" => content_type }
|
md5 = Digest::MD5.base64digest(data)
|
||||||
auth_headers = auth_headers_for("PUT", url, headers, data)
|
authorization_headers = authorization_headers_for(
|
||||||
|
"PUT", url, md5, content_type
|
||||||
# TODO check if put was successful, e.g. it's returning a 413 directly
|
).merge({ "Content-Type" => content_type, "Content-Md5" => md5 })
|
||||||
# if the back-end does, too (missing CORS headers in that case)
|
res = RestClient.put(url, data, authorization_headers)
|
||||||
res = RestClient.put(url, data, headers.merge(auth_headers))
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
res.headers[:etag].delete('"'),
|
res.headers[:etag].delete('"'),
|
||||||
@ -43,24 +32,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"]
|
||||||
auth_headers = auth_headers_for("GET", url, headers)
|
authorization_headers = authorization_headers_for("GET", url)
|
||||||
RestClient.get(url, headers.merge(auth_headers), &block)
|
RestClient.get(url, authorization_headers.merge(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
|
||||||
auth_headers = auth_headers_for("HEAD", url)
|
authorization_headers = authorization_headers_for("HEAD", url)
|
||||||
RestClient.head(url, auth_headers, &block)
|
RestClient.head(url, authorization_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
|
||||||
auth_headers = auth_headers_for("DELETE", url)
|
authorization_headers = authorization_headers_for("DELETE", url)
|
||||||
RestClient.delete(url, auth_headers)
|
RestClient.delete(url, authorization_headers)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -78,11 +67,38 @@ module RemoteStorage
|
|||||||
return found
|
return found
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_headers_for(http_method, url, headers = {}, data = nil)
|
# This is using the S3 authorizations, not the newer AW V4 Signatures
|
||||||
signature = s3_signer.sign_request(
|
# (https://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
|
||||||
http_method: http_method, url: url, headers: headers, body: data
|
def authorization_headers_for(http_verb, url, md5 = nil, content_type = nil)
|
||||||
)
|
url = File.join("/", url.gsub(base_url, ""))
|
||||||
signature.headers
|
date = Time.now.httpdate
|
||||||
|
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
|
||||||
@ -90,7 +106,8 @@ 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
|
||||||
|
@ -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.erb'
|
config_file 'config.yml'
|
||||||
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, Content-Type',
|
'Access-Control-Expose-Headers' => 'ETag, Content-Length, Content-Range',
|
||||||
'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'
|
||||||
|
@ -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
|
||||||
|
@ -8,6 +8,7 @@ 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"
|
||||||
@ -35,22 +36,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'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user