url_signer 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49fa2bf8648727c23d178c1e56fe48292ad9072f
4
+ data.tar.gz: 838502de43dea1e2811b34d84c5f45c3855980d7
5
+ SHA512:
6
+ metadata.gz: af6a065fff64fcc206865a2efd0811dbd7e70ccb559ba2ac59afc6fbf8c9311c32fbb9e9135b3e7b911edba5b20c29a77c54fea1828255e4fb0ce2779299eaa4
7
+ data.tar.gz: 8b12be5dafbde8ae03e3ec96c515a543b807b0283d551335502724168427b21fe38cb744d462b5225c4273032fa7484f4b68cb1c6d2b0460c92a60e58fc8c1d9
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in url_sign.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Aurélien Noce
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,48 @@
1
+ # UrlSign
2
+
3
+ Quickly generate and verify signed urls.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'url_signer'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install url_signer
20
+
21
+ ## Usage
22
+
23
+ ### URL signing
24
+
25
+ To convert a URL into a signed url, pass it to `UrlSigner.sign`, passing it either a string of an instance of `URI`.
26
+
27
+ ```ruby
28
+ >>> signed_url = UrlSigner.sign('http://google.fr?q=test', key='mykey')
29
+ ```
30
+
31
+ the returned value `signed_url` is an instance of `URI`.
32
+
33
+ ### URL verification
34
+
35
+ Given a signed URL, you can check its authenticity by calling `UrlSigner.valid?` on it:
36
+
37
+ ```ruby
38
+ >>> UrlSigner.valid?(signed_url)
39
+ true
40
+ ```
41
+
42
+ ## Contributing
43
+
44
+ 1. Fork it ( https://github.com/[my-github-username]/url_sign/fork )
45
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
46
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
47
+ 4. Push to the branch (`git push origin my-new-feature`)
48
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,55 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'digest/hmac'
4
+ require 'digest/sha1'
5
+
6
+ module UrlSigner
7
+ class Base < Struct.new(:url, :key, :hash_method)
8
+
9
+ def initialize(url, key: nil, hash_method: nil)
10
+ # load and check url
11
+ url = URI.parse(url) if url.kind_of?(String)
12
+ raise "expecting a String or URI instance" unless url.kind_of?(URI)
13
+
14
+ # load and check signing key
15
+ key ||= ENV['URL_SIGNING_KEY']
16
+ raise "You need to provided a signing key to your UrlSigner instance" unless key
17
+
18
+ # load the hash method
19
+ hash_method ||= Digest::SHA1
20
+
21
+ super(url, key, hash_method)
22
+ end
23
+
24
+ def signature
25
+ compute_signature(url.host, url.path, params)
26
+ end
27
+
28
+ protected
29
+
30
+ def params
31
+ @params ||= begin
32
+ raw_params = CGI.parse(query || '')
33
+ Hash[raw_params.map { |k,v| v.size == 1 ? [k, v[0]] : [k, v] }]
34
+ end
35
+ end
36
+
37
+ def query
38
+ url.query
39
+ end
40
+
41
+ def compute_signature(host, path, params)
42
+ keys = params.keys.sort
43
+ query_string = keys.map { |k| "#{CGI.escape(k)}=#{CGI.escape(params[k])}" }.join
44
+ base_string = "#{CGI.escape(host)}&#{CGI.escape(path)}&#{CGI.escape(query_string)}"
45
+
46
+ return Digest::HMAC.hexdigest(base_string, key, hash_method)
47
+ end
48
+
49
+ def signed?
50
+ params.has_key?('signature')
51
+ end
52
+
53
+
54
+ end
55
+ end
@@ -0,0 +1,23 @@
1
+ require 'url_signer/base'
2
+
3
+ module UrlSigner
4
+ class Signer < Base
5
+
6
+ def sign
7
+ raise "this URL is already signed !" if signed?
8
+
9
+ # build new url
10
+ signed_url = url.dup
11
+ signed_url.query = extended_query
12
+ signed_url
13
+ end
14
+
15
+ private
16
+
17
+ def extended_query
18
+ base_query = query ? query + '&' : ''
19
+ base_query + "signature=#{signature}"
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ require 'url_signer/base'
2
+
3
+ module UrlSigner
4
+ class Verifier < Base
5
+
6
+ def valid?
7
+ return false unless signed?
8
+
9
+ # extract signature from params
10
+ remaining_params = params.dup
11
+ provided_signature = remaining_params.delete('signature')
12
+
13
+ # recompute the signature using the secret key
14
+ recomputed_signature = compute_signature(url.host, url.path, remaining_params)
15
+
16
+ safe_compare(provided_signature, recomputed_signature)
17
+ end
18
+
19
+ private
20
+
21
+ def safe_compare(signature, other_signature)
22
+ hash_method.digest(signature) == hash_method.digest(other_signature)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module UrlSigner
2
+ VERSION = "0.1"
3
+ end
data/lib/url_signer.rb ADDED
@@ -0,0 +1,44 @@
1
+ module UrlSigner
2
+ autoload :Base, 'url_signer/base'
3
+ autoload :Signer, 'url_signer/signer'
4
+ autoload :Verifier, 'url_signer/verifier'
5
+
6
+ module_function
7
+
8
+ # Returns a new <tt>URI</tt> instance by appending a <tt>signature</tt> parameter to the query of +url+.
9
+ # The method accepts that +url+ can be either a <tt>String</tt> or a <tt>URI</tt> instance.
10
+ #
11
+ # signed_url = UrlSigner.sign('http://google.fr&q=test') # => <URI::HTTP...>
12
+ #
13
+ # The following key/value parameters can be given to +options+:
14
+ # * <tt>:key</tt> - the secret key used for encryption
15
+ # * <tt>:hash_method</tt> - the hash function to pass to <tt>Digest::HMAC</tt>. Defaults to <tt>Digest::SHA1</tt>.
16
+ #
17
+ # Note that if a +URL_SIGNING_KEY+ environment variable is defined, it will be used as a default value for the +:key+ option.
18
+ def sign(url, *options)
19
+ temp_signer = UrlSigner::Signer.new(url, *options)
20
+ temp_signer.sign
21
+ end
22
+
23
+ # Verify the authenticity of the +url+ by checking the value of the <tt>signature</tt> query parameter (if present).
24
+ # The method accepts that +url+ can be either a <tt>String</tt> or a <tt>URI</tt> instance.
25
+ #
26
+ # The following key/value parameters can be given to +options+:
27
+ # * <tt>:key</tt> - the secret key used for encryption
28
+ # * <tt>:hash_method</tt> - the hash function to pass to <tt>Digest::HMAC</tt>. Defaults to <tt>Digest::SHA1</tt>.
29
+ #
30
+ # Note that if a +URL_SIGNING_KEY+ environment variable is defined, it will be used as a default value for the +:key+ option.
31
+ #
32
+ # ==== Examples
33
+ #
34
+ # dummy_url = 'http://google.fr?q=test
35
+ # UrlSigner.valid?(dummy_url) # => false
36
+ #
37
+ # signed_url = UrlSigner.sign('http://google.fr&q=test')
38
+ # UrlSigner.valid?(signed_url) # => true
39
+ def valid?(url, *options)
40
+ temp_verifier = UrlSigner::Verifier.new(url, *options)
41
+ temp_verifier.valid?
42
+ end
43
+
44
+ end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ module UrlSigner
4
+ describe Base do
5
+ let(:url_string) { 'http://mysite.com/my/path?test=1&retest=2' }
6
+ let(:url) { URI.parse(url_string) }
7
+ let(:params) { CGI.parse(url.query) }
8
+
9
+ let(:base) { Base.new(url, key: 'testkey') }
10
+
11
+ describe "#signature:" do
12
+ let(:url_with_other_params) { URI.parse('http://mysite.com/my/path?test=2&retest=2') }
13
+ let(:url_with_other_domain) { URI.parse('http://myothersite.com/my/path?test=1&retest=2') }
14
+ let(:url_with_other_path) { URI.parse('http://mysite.com/my/other/path?test=1&retest=2') }
15
+
16
+ it "depends on the signing key" do
17
+ other = Base.new(url, key: 'othertestkey')
18
+ expect(other.signature).not_to eq(base.signature)
19
+ end
20
+
21
+ it "depends on the parameters" do
22
+ other = Base.new(url_with_other_params, key: 'testkey')
23
+ expect(other.signature).not_to eq(base.signature)
24
+ end
25
+
26
+ it "depends on the domain" do
27
+ other = Base.new(url_with_other_domain, key: 'testkey')
28
+ expect(other.signature).not_to eq(base.signature)
29
+ end
30
+
31
+ it "depends on the path" do
32
+ other = Base.new(url_with_other_path, key: 'testkey')
33
+ expect(other.signature).not_to eq(base.signature)
34
+ end
35
+ end # #signature
36
+
37
+ describe "url parameter:" do
38
+
39
+ context "when the url is given as a string," do
40
+ let(:base_from_string) { Base.new(url_string, key: 'testkey') }
41
+
42
+ it "builds a new signer" do
43
+ expect { Base.new(url_string, key: 'testkey') }.not_to raise_error
44
+ end
45
+
46
+ it "works as usual" do
47
+ expect{ base_from_string.signature }.not_to raise_error
48
+ end
49
+
50
+ it "is equivalent to the save Signer built from a URI instance" do
51
+ expect(base_from_string.signature).to eq(base.signature)
52
+ end
53
+
54
+ it "raise if the URL can not be parsed" do
55
+ expect{ Base.new('not a good idea') }.to raise_error
56
+ end
57
+
58
+ end
59
+
60
+ it "raise a descriptive message if an invalid type is passed" do
61
+ expect{ Signer.new([ 'not a good idea' ]) }.to raise_error
62
+ end
63
+
64
+ end # url parameter:
65
+
66
+ end
67
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ module UrlSigner
4
+ describe Signer do
5
+ let(:url_string) { 'http://mysite.com/my/path?test=1&retest=2' }
6
+ let(:url) { URI.parse(url_string) }
7
+ let(:params) { CGI.parse(url.query) }
8
+
9
+ let(:signer) { Signer.new(url, key: 'testkey') }
10
+
11
+ describe "#sign:" do
12
+ let(:signed_url) { signer.sign }
13
+ let(:signed_params) { CGI.parse(signed_url.query) }
14
+
15
+ it "returns a new url" do
16
+ expect(signed_url).to be_a(URI)
17
+ expect(signed_url).not_to be(url)
18
+ end
19
+
20
+ it "keeps all the incoming parameters" do
21
+ signed_params.delete('signature')
22
+ expect(signed_params).to eq(params)
23
+ end
24
+
25
+ it "adds a signature parameter" do
26
+ expect(signed_params).to have_key('signature')
27
+ end
28
+
29
+ describe "signature param" do
30
+ let(:signature_param) { signed_params['signature'][0] }
31
+
32
+ it "contains the computed signature" do
33
+ expect(signature_param).to eq(signer.signature)
34
+ end
35
+
36
+ end # signature param
37
+
38
+ end # #sign
39
+
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'uri'
4
+ require 'cgi'
5
+ require 'rspec'
6
+ require 'url_signer'
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe UrlSigner do
4
+ let(:url) { 'http://google.fr' }
5
+ let(:fake_signer) { double(sign: true, valid?: true) }
6
+
7
+ FORWARDED_METHOD = {
8
+ sign: ::UrlSigner::Signer,
9
+ valid?: ::UrlSigner::Verifier
10
+ }
11
+ FORWARDED_METHOD.each do |method, klass|
12
+ describe ".#{method}:" do
13
+
14
+ it "builds a new #{klass} instance" do
15
+ expect(klass).to receive(:new).with(url, key: 'toto').and_return(fake_signer)
16
+
17
+ # call method by name dynamically
18
+ UrlSigner.send(method, url, key: 'toto')
19
+ end
20
+
21
+ it "calls ##{method} on the new #{klass} instance" do
22
+ allow(klass).to receive(:new).and_return(fake_signer)
23
+ expect(fake_signer).to receive(method)
24
+
25
+ # call method by name dynamically
26
+ UrlSigner.send(method, url, key: 'toto')
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ module UrlSigner
4
+ describe Verifier do
5
+ let(:url_string) { 'http://mysite.com/my/path?test=1&retest=2' }
6
+ let(:url) { URI.parse(url_string) }
7
+ let(:params) { CGI.parse(url.query) }
8
+
9
+ let(:signer) { Signer.new(url, key: 'testkey') }
10
+
11
+ describe "#valid?:" do
12
+ let(:signed_url) { signer.sign }
13
+
14
+ context "when an url has been signed," do
15
+ let(:verifier) { Verifier.new(signed_url, key: 'testkey') }
16
+
17
+ it "can verify the url" do
18
+ expect(verifier.valid?).to be(true)
19
+ end
20
+
21
+ context "when the signed url has been modified," do
22
+ # will overload signed_url in this context
23
+ let(:signed_url) {
24
+ vanilla_url = signer.sign
25
+ vanilla_url.query += '&toto=titi'
26
+ vanilla_url
27
+ }
28
+
29
+ it "does not verify the url" do
30
+ expect(verifier.valid?).to be(false)
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ context "when an url has not been signed," do
37
+ let(:verifier) { Verifier.new(url, key: 'testkey') }
38
+
39
+ it "does not verify the url" do
40
+ expect(verifier.valid?).to be(false)
41
+ end
42
+ end
43
+
44
+ end # #valid?
45
+
46
+ end
47
+ end
48
+
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'url_signer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "url_signer"
8
+ spec.version = UrlSigner::VERSION
9
+ spec.authors = ["Aurélien Noce"]
10
+ spec.email = ["aurnoce@gmail.com"]
11
+ spec.summary = %q{Sign and verify URLs}
12
+ spec.description = %q{Simple solution (2 methods) to sign URLs and verify the generated URLs. Use HAMC/SHA1 for signing by default but can be configured.}
13
+ spec.homepage = "https://github.com/ushu/url_signer"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
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.6"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.1"
24
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: url_signer
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Aurélien Noce
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-30 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.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.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: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ description: Simple solution (2 methods) to sign URLs and verify the generated URLs.
56
+ Use HAMC/SHA1 for signing by default but can be configured.
57
+ email:
58
+ - aurnoce@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - .gitignore
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - lib/url_signer.rb
69
+ - lib/url_signer/base.rb
70
+ - lib/url_signer/signer.rb
71
+ - lib/url_signer/verifier.rb
72
+ - lib/url_signer/version.rb
73
+ - spec/base_spec.rb
74
+ - spec/signer_spec.rb
75
+ - spec/spec_helper.rb
76
+ - spec/url_signer_spec.rb
77
+ - spec/verifier_spec.rb
78
+ - url_signer.gemspec
79
+ homepage: https://github.com/ushu/url_signer
80
+ licenses:
81
+ - MIT
82
+ metadata: {}
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.0.14
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Sign and verify URLs
103
+ test_files:
104
+ - spec/base_spec.rb
105
+ - spec/signer_spec.rb
106
+ - spec/spec_helper.rb
107
+ - spec/url_signer_spec.rb
108
+ - spec/verifier_spec.rb
109
+ has_rdoc: