s3_browser_uploads 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d11949c33c4d40cd6f60105ab513b4a86e3a0174
4
+ data.tar.gz: ebc1978bc5ce720a5f935ce489d19c0359ac2502
5
+ SHA512:
6
+ metadata.gz: f28057c0ddec32a220adfbf448240a816ea1fc85c240d1a34b997cb2fc27f1255f12ebe119d3e0381bc6c4521e53c0b5ba67692fdd89737de37fefb8eb7ea598
7
+ data.tar.gz: 8ca98453654043da7aec1c5d0ff6bfde99ab6ae35012955a5fd76d51578c58b8cbf704d681347f7dc3438e320ddf4f62283b43ab225f9269214c16bf7c408976
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in s3_browser_uploads.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Frederick Cheung
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # S3BrowserUploads
2
+
3
+ Easy straight-to-s3 uploads from your browser for rails. Takes care of policy documents and all that jazz
4
+
5
+ ## Example Usage
6
+
7
+ In your controller
8
+
9
+ @form_definition = S3BrowserUploads::FormDefinition.new :aws_access_key_id => 'Somekey'
10
+ @form_definition.add_field('x-amz-server-side-encryption', 'AES256')
11
+ @form_definition.add_field('key', 'users/fred/${filename}')
12
+ @form_definition.add_condition('key', 'starts-with' => 'users/fred/')
13
+
14
+ In your view
15
+
16
+ <%= s3_form @form_definition do %>
17
+
18
+ <%= file_field_tag :file %>
19
+ <%= submit_tag 'Upload' %>
20
+ <% end %>
21
+
22
+
23
+ ## Details
24
+
25
+ Every field in your form must be signed as part of the policy document in order for S3 to accept the upload.
26
+
27
+ Call `add_field` to add a hidden field both to the form and to the policy document. If you want to add the field yourself then call `add_condition` to add the field to the policy document. For example if you wanted to allow the user to choose the acl for the file then you would do
28
+
29
+ @form_definition.add_condition 'acl', 'starts-with' => ''
30
+
31
+ This adds `acl` to the policy document and states that it can take any value. Then you'd need to add an input to the form that would allow the user to choose the acl.
32
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,10 @@
1
+ require "s3_browser_uploads/version"
2
+ require "s3_browser_uploads/form_definition"
3
+
4
+ module S3BrowserUploads
5
+ # Your code goes here...
6
+ end
7
+
8
+ if defined?(Rails)
9
+ require 's3_browser_uploads/railtie'
10
+ end
@@ -0,0 +1,85 @@
1
+ require 'time'
2
+ require 'base64'
3
+ require 'json'
4
+ module S3BrowserUploads
5
+ class FormDefinition
6
+ attr_accessor :region, :aws_secret_access_key, :expires, :fields, :conditions
7
+
8
+
9
+ def aws_access_key_id= value
10
+ fields['AWSAccessKeyID'] = value #access key id is not included in the policy
11
+ end
12
+
13
+ def aws_access_key_id
14
+ fields['AWSAccessKeyID'] #access key id is not included in the policy
15
+ end
16
+
17
+
18
+ def aws_session_token
19
+ fields['x-amz-security-token']
20
+ end
21
+
22
+ def aws_session_token= value
23
+ add_field 'x-amz-security-token', value
24
+ end
25
+
26
+
27
+ def bucket
28
+ fields['bucket']
29
+ end
30
+
31
+ def bucket= value
32
+ add_field 'bucket', value
33
+ end
34
+
35
+ def add_field key, value
36
+ fields[key] = value
37
+ add_condition key, value
38
+ value
39
+ end
40
+
41
+ def add_condition key, condition
42
+ @conditions[key] = case condition
43
+ when String then {key => condition}
44
+ when Hash then
45
+ [condition.keys.first, "$#{key}", condition.values.first]
46
+ when Range then
47
+ [key, condition.begin, condition.end]
48
+ else
49
+ raise ArgumentError, "unknown condition type #{condition}"
50
+ end
51
+ end
52
+
53
+ def restrict_content_length range
54
+ add_condition 'content-length-range', range
55
+ end
56
+
57
+ def initialize(options={})
58
+ @fields = {}
59
+ @conditions = {}
60
+ options.each {|key, value| public_send("#{key}=", value)}
61
+ @digest = OpenSSL::Digest::Digest.new('sha1')
62
+ @hmac = lambda {|data| OpenSSL::HMAC.digest(@digest, @aws_secret_access_key, data)}
63
+ end
64
+
65
+ def signature
66
+ Base64.strict_encode64(@hmac[encoded_policy])
67
+ end
68
+
69
+ def endpoint
70
+ "https://#{bucket}.s3-#{region}.amazonaws.com"
71
+ end
72
+
73
+ def encoded_policy
74
+ Base64.strict_encode64(policy_document.to_json)
75
+ end
76
+
77
+ def policy_document
78
+ {
79
+ 'expiration' => expires.xmlschema,
80
+ 'conditions' => @conditions.values
81
+ }
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ module S3BrowserUploads
2
+ class Railtie < ::Rails::Railtie
3
+ initializer "will_paginate" do |app|
4
+ ActiveSupport.on_load :action_view do
5
+ require 's3_browser_uploads/view_helpers'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module S3BrowserUploads
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ module S3BrowserUploads
2
+ module ViewHelpers
3
+ def s3_form form_definition, html_options={}, &block
4
+ options = {'action' => form_definition.endpoint, 'method' => 'POST', 'enctype' => "multipart/form-data",'accept-charset' => "UTF-8"}
5
+ options.merge! html_options
6
+
7
+ output = ActiveSupport::SafeBuffer.new
8
+ output.safe_concat(tag(:form, options, true).html_safe)
9
+ output << hidden_field_tag( 'x-ignore-utf8', '&#x2713;'.html_safe)
10
+ form_definition.fields.each do |name, value|
11
+ output << hidden_field_tag( name, value)
12
+ end
13
+ output << hidden_field_tag('policy', form_definition.encoded_policy)
14
+ output << hidden_field_tag('signature', form_definition.signature)
15
+ output << capture(&block)
16
+ output.safe_concat("</form>")
17
+ end
18
+ ::ActionView::Base.send :include, self
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 's3_browser_uploads/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "s3_browser_uploads"
8
+ spec.version = S3BrowserUploads::VERSION
9
+ spec.authors = ["Frederick Cheung"]
10
+ spec.email = ["frederick.cheung@gmail.com"]
11
+ spec.description = %q{Easy straight-to-s3 uploads from your browser}
12
+ spec.summary = %q{Easy straight-to-s3 uploads from your browser}
13
+ spec.homepage = "https://github.com/fcheung/s3_browser_uploads"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.13.0"
24
+ spec.add_development_dependency "rspec-rails", "~> 2.13.0"
25
+ spec.add_development_dependency "rails", "~> 3.2.13"
26
+ spec.add_development_dependency "capybara", "~> 2.1.0"
27
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3BrowserUploads::FormDefinition do
4
+
5
+ let(:expires_at) {Time.now + 1800}
6
+ [:region, :aws_access_key_id, :aws_secret_access_key, :aws_session_token, :bucket, :expires].each do |key|
7
+ it "should set #{key} from hash passed to" do
8
+ form = S3BrowserUploads::FormDefinition.new(key => 'foo')
9
+ form.send(key).should == 'foo'
10
+ end
11
+ end
12
+
13
+ describe 'endpoint' do
14
+ it 'should return the url for the bucket' do
15
+ S3BrowserUploads::FormDefinition.new(:region => 'eu-west-1', :bucket => 'some-bucket').endpoint.should ==
16
+ "https://some-bucket.s3-eu-west-1.amazonaws.com"
17
+ end
18
+ end
19
+
20
+ describe 'aws_session_token' do
21
+ it 'should be added as the x-amz-security-token field' do
22
+ form = S3BrowserUploads::FormDefinition.new(:region => 'eu-west-1', :bucket => 'some-bucket', :aws_session_token => 'token')
23
+ form.fields['x-amz-security-token'].should == 'token'
24
+ form.conditions['x-amz-security-token'].should == {'x-amz-security-token' => 'token'}
25
+ end
26
+ end
27
+ describe 'encoded_policy' do
28
+ subject { S3BrowserUploads::FormDefinition.new(:region => 'eu-west-1', :bucket => 'some-bucket', :expires => expires_at)}
29
+
30
+ its(:encoded_policy) {should == Base64.strict_encode64(subject.policy_document.to_json)}
31
+ its(:encoded_policy) {should_not include("\n") }
32
+
33
+ end
34
+
35
+ describe 'signature' do
36
+ #base64 hmac of the encoded policy
37
+ subject { S3BrowserUploads::FormDefinition.new(:aws_secret_access_key => '123XYZ', :region => 'eu-west-1', :bucket => 'some-bucket', :expires => expires_at)}
38
+ its(:signature) {should == Base64.strict_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), '123XYZ', subject.encoded_policy))}
39
+ context 'with sample data' do
40
+ subject do
41
+ form = S3BrowserUploads::FormDefinition.new(:aws_secret_access_key => '123XYZ', :region => 'eu-west-1', :bucket => 'some-bucket', :expires => Time.utc(2012,1,1,1,1,1))
42
+ form.add_field 'acl', 'public-read'
43
+ form.add_condition 'key', 'starts-with' => 'users/fred/'
44
+ form
45
+ end
46
+ its(:signature) {should == 'uxKt7EExauvCe41l6O44RnMlZmQ='}
47
+ end
48
+
49
+ end
50
+
51
+ describe 'policy_document' do
52
+ let(:form) { S3BrowserUploads::FormDefinition.new(:region => 'eu-west-1', :bucket => 'some-bucket', :expires => expires_at)}
53
+
54
+ it 'should contain expiration and conditions' do
55
+ form.policy_document.keys.should =~ %w(conditions expiration)
56
+ end
57
+
58
+ it 'should set expiration to the xml schema representation of the expiry date' do
59
+ form.policy_document['expiration'].should == expires_at.xmlschema
60
+ end
61
+
62
+ context 'with no conditions added' do
63
+ it 'should have a condition on the bucket' do
64
+ form.policy_document['conditions'].should == [{'bucket' => 'some-bucket'}]
65
+ end
66
+ end
67
+
68
+ context 'with a strict condition added' do
69
+ it 'should have the condition' do
70
+ form.add_condition 'key', 'abc'
71
+ form.policy_document['conditions'].should =~ [{'bucket' => 'some-bucket'}, {'key' => 'abc'}]
72
+ end
73
+ end
74
+
75
+ context 'with a starts-with condition' do
76
+ it 'should have the condition' do
77
+ form.add_condition 'key', 'starts-with' => 'abc'
78
+ form.policy_document['conditions'].should =~ [{'bucket' => 'some-bucket'}, ['starts-with', '$key', 'abc']]
79
+ end
80
+ end
81
+
82
+
83
+ context 'with a content-length condition' do
84
+ it 'should have the condition' do
85
+ form.restrict_content_length 0..1024
86
+ form.policy_document['conditions'].should =~ [{'bucket' => 'some-bucket'}, ['content-length-range', 0,1024]]
87
+ end
88
+ end
89
+
90
+ context 'with an eq condition' do
91
+ it 'should have the condition' do
92
+ form.add_condition 'key', 'eq' => 'abc'
93
+ form.policy_document['conditions'].should =~ [{'bucket' => 'some-bucket'}, ['eq', '$key', 'abc']]
94
+ end
95
+ end
96
+
97
+ context 'when a field has been added' do
98
+ it 'should add a strict match condition on the field' do
99
+ form.add_field('acl', 'private')
100
+ form.policy_document['conditions'].should == [{'bucket' => 'some-bucket'}, {'acl' => 'private'}]
101
+ end
102
+
103
+ it 'should the condition to be overriden' do
104
+ form.add_field('key', 'users/bob/${filename}')
105
+ form.add_condition('key', 'starts-with' => 'users/bob/')
106
+ form.policy_document['conditions'].should == [{'bucket' => 'some-bucket'}, ['starts-with', '$key', 'users/bob/']]
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,18 @@
1
+ require 'bundler/setup'
2
+ require 's3_browser_uploads'
3
+ require 'action_controller'
4
+ module Rails
5
+ module VERSION
6
+ STRING = '3.2.13'
7
+ end
8
+ end
9
+ require 'rspec/rails/adapters'
10
+ require 'rspec/rails/example/rails_example_group'
11
+ require 'rspec/rails/example/view_example_group'
12
+ require 'rspec/rails/example/helper_example_group'
13
+ require 's3_browser_uploads/view_helpers'
14
+ require 'capybara/rspec'
15
+
16
+ RSpec.configure do |config|
17
+ config.mock_with :rspec
18
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3BrowserUploads::ViewHelpers, :type => 'helper' do
4
+ include Capybara::RSpecMatchers
5
+ include RSpec::Rails::HelperExampleGroup
6
+ let(:form_definition) do
7
+ S3BrowserUploads::FormDefinition.new(:region => 'eu-west-1',
8
+ :bucket => 'some-bucket',
9
+ :expires => Time.now + 1800,
10
+ :aws_access_key_id => 'AnAccessKey',
11
+ :aws_secret_access_key => 'ASecretKey')
12
+ end
13
+
14
+ describe 's3_form' do
15
+ let(:content) {helper.s3_form(form_definition) {}}
16
+
17
+ describe 'form tag' do
18
+ subject {Capybara.string(content).find('form')}
19
+ its([:action]) { should == form_definition.endpoint }
20
+ its([:enctype]) { should == 'multipart/form-data' }
21
+ its([:method]) { should == 'POST' }
22
+
23
+ context 'html options are passed' do
24
+ it 'should add them to the form' do
25
+ helper.s3_form(form_definition, :id => 'a-form'){}.should have_css 'form#a-form'
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'form contents' do
31
+ subject {content}
32
+ it 'should include a utf8 enforcer tag' do
33
+ should have_hidden_input("x-ignore-utf8").with_value("\u2713")
34
+ end
35
+
36
+ it { should have_hidden_input('AWSAccessKeyID').with_value("AnAccessKey") }
37
+ it { should have_hidden_input('signature').with_value(form_definition.signature) }
38
+ it { should have_hidden_input('policy').with_value(form_definition.encoded_policy) }
39
+ it { should have_hidden_input('bucket').with_value(form_definition.bucket) }
40
+
41
+
42
+ context 'a field was added to the form' do
43
+ before(:each) { form_definition.add_field 'Content-Disposition', 'inline'}
44
+ it { should have_hidden_input('Content-Disposition').with_value('inline')}
45
+ end
46
+
47
+ context 'the block has content' do
48
+ let(:content) {helper.s3_form(form_definition) {submit_tag 'Upload'}}
49
+
50
+ it 'should include it in the form' do
51
+ content.should have_css('input[type=submit][value=Upload]')
52
+ end
53
+
54
+ it 'should include it after the autogenerated inputs' do
55
+ Capybara.string(content).all('input').last['type'].should == 'submit'
56
+ Capybara.string(content).all('input').last['value'].should == 'Upload'
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+
63
+ RSpec::Matchers.define :have_hidden_input do |name|
64
+
65
+ chain :with_value do |value|
66
+ @value = value
67
+ end
68
+
69
+ match do |x|
70
+ selector = "input[type=hidden][name='#{name}']"
71
+ selector += "[value='#{@value}']" if @value
72
+ x.should have_css(selector)
73
+ end
74
+ end
75
+
76
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3_browser_uploads
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Frederick Cheung
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 2.13.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.13.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.13.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 2.13.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 3.2.13
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.13
83
+ - !ruby/object:Gem::Dependency
84
+ name: capybara
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 2.1.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 2.1.0
97
+ description: Easy straight-to-s3 uploads from your browser
98
+ email:
99
+ - frederick.cheung@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - lib/s3_browser_uploads.rb
110
+ - lib/s3_browser_uploads/form_definition.rb
111
+ - lib/s3_browser_uploads/railtie.rb
112
+ - lib/s3_browser_uploads/version.rb
113
+ - lib/s3_browser_uploads/view_helpers.rb
114
+ - s3_browser_uploads.gemspec
115
+ - spec/form_definition_spec.rb
116
+ - spec/spec_helper.rb
117
+ - spec/view_helpers_spec.rb
118
+ homepage: https://github.com/fcheung/s3_browser_uploads
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.0.3
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Easy straight-to-s3 uploads from your browser
142
+ test_files:
143
+ - spec/form_definition_spec.rb
144
+ - spec/spec_helper.rb
145
+ - spec/view_helpers_spec.rb