23 Commits

Author SHA1 Message Date
a5da2fbd40 Add Gitea action for CI
All checks were successful
Tests / test (6, 2.7) (pull_request) Has been cancelled
Tests / test (6, 3.0) (pull_request) Has been cancelled
Tests / test (6, 3.1) (pull_request) Has been cancelled
Tests / test (7, 2.7) (pull_request) Has been cancelled
Tests / test (7, 3.0) (pull_request) Has been cancelled
Tests / test (7, 3.1) (pull_request) Has been cancelled
2023-11-21 15:45:13 +01:00
df1a3e6512 Add Drone CI config
Some checks failed
continuous-integration/drone Build is failing
2023-11-21 15:38:15 +01:00
8b6f201a0b Merge pull request 'Switch to AWS v4 signatures' (#5) from feature/aws_v4_signatures into master
All checks were successful
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
15 changed files with 190 additions and 148 deletions

9
.drone.yml Normal file
View File

@@ -0,0 +1,9 @@
kind: pipeline
name: default
steps:
- name: test
image: ruby
commands:
- bundle install --jobs=3 --retry=3
- rake test

33
.gitea/workflows/ruby.yml Normal file
View File

@@ -0,0 +1,33 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.7', '3.0', '3.1']
redis-version: [6, 7]
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.erb.example config.yml.erb
- name: Run tests
run: bundle exec rake test

View File

@@ -11,13 +11,11 @@ permissions:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.5', '2.6', '2.7']
redis-version: [4, 5, 6]
ruby-version: ['2.7', '3.0', '3.1']
redis-version: [6, 7]
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
@@ -30,6 +28,6 @@ jobs:
with:
redis-version: ${{ matrix.redis-version }}
- name: Configure
run: cp config.yml.example.s3 config.yml
run: cp config.yml.erb.example config.yml.erb
- name: Run tests
run: bundle exec rake test

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
config.yml
config.yml.erb
cs_credentials.json
pids
.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"]

View File

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

View File

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

View File

@@ -12,10 +12,9 @@ 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
OAuth dialog for remoteStorage authorizations and persist the tokens in Redis.
If you have any questions about this program, drop by #remotestorage on
Freenode, or [post to the RS
If you have any questions about this program, please [post to the RS
forums](https://community.remotestorage.io/c/server-development), and we'll
happily answer them.
gladly answer them.
## Contributing
@@ -23,5 +22,5 @@ We love pull requests. If you want to submit a patch:
* Fork the project.
* 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.

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

@@ -1,8 +1,8 @@
require "remote_storage/rest_provider"
require "digest"
require "base64"
require "cgi"
require "openssl"
require "webrick/httputils"
module RemoteStorage
class S3
@@ -10,18 +10,26 @@ module RemoteStorage
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
)
end
# S3 already wraps the ETag with quotes
def format_etag(etag)
etag
end
def do_put_request(url, data, content_type)
deal_with_unauthorized_requests do
md5 = Digest::MD5.base64digest(data)
authorization_headers = authorization_headers_for(
"PUT", url, md5, content_type
).merge({ "Content-Type" => content_type, "Content-Md5" => md5 })
res = RestClient.put(url, data, authorization_headers)
headers = { "Content-Type" => content_type }
auth_headers = auth_headers_for("PUT", url, headers, data)
res = RestClient.put(url, data, headers.merge(auth_headers))
return [
res.headers[:etag].delete('"'),
@@ -32,24 +40,24 @@ module RemoteStorage
def do_get_request(url, &block)
deal_with_unauthorized_requests do
headers = { }
headers = {}
headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"]
authorization_headers = authorization_headers_for("GET", url)
RestClient.get(url, authorization_headers.merge(headers), &block)
auth_headers = auth_headers_for("GET", url, headers)
RestClient.get(url, headers.merge(auth_headers), &block)
end
end
def do_head_request(url, &block)
deal_with_unauthorized_requests do
authorization_headers = authorization_headers_for("HEAD", url)
RestClient.head(url, authorization_headers, &block)
auth_headers = auth_headers_for("HEAD", url)
RestClient.head(url, auth_headers, &block)
end
end
def do_delete_request(url)
deal_with_unauthorized_requests do
authorization_headers = authorization_headers_for("DELETE", url)
RestClient.delete(url, authorization_headers)
auth_headers = auth_headers_for("DELETE", url)
RestClient.delete(url, auth_headers)
end
end
@@ -67,38 +75,15 @@ module RemoteStorage
return found
end
# This is using the S3 authorizations, not the newer AW V4 Signatures
# (https://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
def authorization_headers_for(http_verb, url, md5 = nil, content_type = nil)
url = File.join("/", url.gsub(base_url, ""))
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)
def auth_headers_for(http_method, url, headers = {}, data = nil)
signature = s3_signer.sign_request(
http_method: http_method, url: url, headers: headers, body: data
)
signature.headers
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)
CGI.escape(s).gsub('%5B', '[').gsub('%5D', ']')
end
def base_url
@@ -106,8 +91,7 @@ module RemoteStorage
end
def container_url_for(user)
"#{base_url}#{settings.s3["bucket"]}/#{user}"
"#{base_url}/#{settings.s3["bucket"]}/#{user}"
end
end
end

View File

@@ -18,7 +18,7 @@ class LiquorCabinet < Sinatra::Base
register Sinatra::ConfigFile
set :environments, %w{development test production staging}
config_file 'config.yml'
config_file 'config.yml.erb'
end
configure :development do
@@ -64,7 +64,7 @@ class LiquorCabinet < Sinatra::Base
headers 'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, PUT, DELETE',
'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'
headers['Access-Control-Allow-Origin'] = env["HTTP_ORIGIN"] if env["HTTP_ORIGIN"]
headers['Cache-Control'] = 'no-cache'

11
run-dev.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
RACK_ENV=development \
REDIS_HOST=localhost \
REDIS_PORT=6379 \
REDIS_DB=1 \
S3_ENDPOINT='http://localhost:9000' \
S3_ACCESS_KEY='dev-key' \
S3_SECRET_KEY='123456789' \
S3_BUCKET=remotestorage \
bundle exec rackup -p 4567

View File

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

View File

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