rotp 4.0.2 → 6.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72d869e33ff8ede2ef4233ed0398b730a1fc14e2b764295b090492bed133c4fc
4
- data.tar.gz: 5ee5f47d3cee494762fcaaabb4dff1dfc247bab7faa0207fc6a3b7ff7b672f05
3
+ metadata.gz: cd976bfa6985075f5e2b76607256d0afbbdf88a82c38cd094d0eaffbb5bce4f2
4
+ data.tar.gz: 70df660f1eca3dd9efc7baa1f53061ba9af1bbb49e4bb6ead507509f6e845d38
5
5
  SHA512:
6
- metadata.gz: 42c5bb89a97375204c3198dd9d07080f4728162307c9b99eb6f7c46152bfa8aa21d7e02699fc911c9991ef6ae17c9465f2c8e438bf223b2097cbc9b72766912b
7
- data.tar.gz: 45dc82dd282328c5e3651a5b660ac327c1d10f07b7ca21025139fbe3edf418b661e1b67648081027e04ea9bce639195ec21d44ada7d6af9ac607984e8fcda47b
6
+ metadata.gz: 7fb326cc887a1a5614c90c492ac43b72188f75caa90fcc50c3338d129abe2efe4f67af88d018c378379806f1bef0c1d0e40fc6c683f4427f40ad411326729022
7
+ data.tar.gz: 4f913bf0693c1cead926bfe625e226fe8277323f93a552459447a792cd27b9189860ab26d3907baafff0e217430b04fb9e8b1829b0795e4c96e83062fac409fb
data/.travis.yml CHANGED
@@ -1,8 +1,9 @@
1
1
  language: ruby
2
- before_install: gem install bundler
2
+ before_install: gem install bundler -v '<2'
3
3
  rvm:
4
- - 2.3.0
5
- - 2.1.0
6
- - 2.0.0
4
+ - 2.7
5
+ - 2.6
6
+ - 2.5
7
+ - 2.3
7
8
  script:
8
9
  - bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  ### Changelog
2
2
 
3
+ ### 6.2.0
4
+
5
+ - Update to expand compatibility with Ruby 3. This was only a change to the
6
+ gemspec, no code changes were necessary.
7
+
8
+ ### 6.1.0
9
+
10
+ - Fixing URI encoding issues again, breaking out into it's own module
11
+ due to the complexity - closes #100 (@atcruice)
12
+ - Add docker-compose.yml to help with easier testing
13
+
14
+ ### 6.0.0
15
+
16
+ - Dropping support for Ruby <2.3 (Major version bump)
17
+ - Fix issue when using --enable-frozen-string-literal Ruby option #95 (jeremyevans)
18
+ - URI Encoding fix #94 (ksuh90)
19
+ - Update gems (rake, addressable)
20
+ - Update Travis tests to include Ruby 2.7
21
+
22
+ ### 5.1.0
23
+
24
+ - Create `random_base32` to perform `random` to avoid breaking changes
25
+ - Still needed to bump to 5.x due to Base32 cleanup
26
+
27
+ ### 5.0.0
28
+
29
+ - Clean up base32 implementation to match Google Autheticator
30
+ - BREAKING `Base32.random_base32` renamed to random
31
+ - The argument is now byte length vs output string length for more precise bit strengths
32
+
33
+ ### 4.1.0
34
+
35
+ - Add a digest option to the CLI #83
36
+ - Fix provisioning URI is README #82
37
+ - Improvements to docs
38
+
3
39
  ### 4.0.2
4
40
 
5
41
  - Fix gemspec requirment for Addressable
data/Dockerfile-2.3 CHANGED
@@ -1,16 +1,10 @@
1
1
  FROM ruby:2.3
2
2
 
3
- # throw errors if Gemfile has been modified since Gemfile.lock
4
- RUN bundle config --global frozen 1
5
-
6
3
  RUN mkdir -p /usr/src/app
7
4
  WORKDIR /usr/src/app
8
5
 
9
6
  COPY Gemfile /usr/src/app/
10
- COPY Gemfile.lock /usr/src/app/
11
7
  COPY . /usr/src/app
12
8
  RUN bundle install
13
9
 
14
-
15
- CMD ["bundler", "exec", "rspec"]
16
-
10
+ CMD ["bundle", "exec", "rspec"]
data/Dockerfile-2.5 ADDED
@@ -0,0 +1,10 @@
1
+ FROM ruby:2.5
2
+
3
+ RUN mkdir -p /usr/src/app
4
+ WORKDIR /usr/src/app
5
+
6
+ COPY Gemfile /usr/src/app/
7
+ COPY . /usr/src/app
8
+ RUN bundle install
9
+
10
+ CMD ["bundle", "exec", "rspec"]
data/Dockerfile-2.6 ADDED
@@ -0,0 +1,11 @@
1
+ FROM ruby:2.6
2
+
3
+ RUN mkdir -p /usr/src/app
4
+ WORKDIR /usr/src/app
5
+
6
+ COPY Gemfile /usr/src/app/
7
+ COPY . /usr/src/app
8
+ RUN bundle install
9
+
10
+ CMD ["bundle", "exec", "rspec"]
11
+
data/Dockerfile-2.7 ADDED
@@ -0,0 +1,11 @@
1
+ FROM ruby:2.7
2
+
3
+ RUN mkdir -p /usr/src/app
4
+ WORKDIR /usr/src/app
5
+
6
+ COPY Gemfile /usr/src/app/
7
+ COPY . /usr/src/app
8
+ RUN bundle install
9
+
10
+ CMD ["bundle", "exec", "rspec"]
11
+
data/Dockerfile-3.0-rc ADDED
@@ -0,0 +1,12 @@
1
+ FROM ruby:3.0-rc
2
+
3
+ RUN mkdir -p /usr/src/app
4
+ WORKDIR /usr/src/app
5
+
6
+ COPY Gemfile /usr/src/app/
7
+ COPY . /usr/src/app
8
+ RUN gem install bundler
9
+ RUN bundle install
10
+
11
+ CMD ["bundle", "exec", "rspec"]
12
+
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/mdp/rotp.svg?branch=master)](https://travis-ci.org/mdp/rotp)
4
4
  [![Gem Version](https://badge.fury.io/rb/rotp.svg)](https://rubygems.org/gems/rotp)
5
+ [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](https://www.rubydoc.info/github/mdp/rotp/master)
5
6
  [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/mdp/rotp/blob/master/LICENSE)
6
7
 
7
8
  A ruby library for generating and validating one time passwords (HOTP & TOTP) according to [RFC 4226](http://tools.ietf.org/html/rfc4226) and [RFC 6238](http://tools.ietf.org/html/rfc6238).
@@ -15,7 +16,20 @@ Many websites use this for [multi-factor authentication](https://www.youtube.com
15
16
  * OpenSSL
16
17
  * Ruby 2.0 or higher
17
18
 
18
- ## Breaking changes in >= 4.0
19
+ ## Breaking changes
20
+
21
+ ### Breaking changes in >= 6.0
22
+
23
+ - Dropping support for Ruby <2.3
24
+
25
+ ### Breaking changes in >= 5.0
26
+
27
+ - `ROTP::Base32.random_base32` is now `ROTP::Base32.random` and the argument
28
+ has changed from secret string length to byte length to allow for more
29
+ precision. There is an alias to allow for `random_base32` for the time being.
30
+ - Cleaned up the Base32 implementation to match Google Authenticator's version.
31
+
32
+ ### Breaking changes in >= 4.0
19
33
 
20
34
  - Simplified API
21
35
  - `verify` now takes options for `drift` and `after`
@@ -56,8 +70,8 @@ hotp.at(1) # => "595254"
56
70
  hotp.at(1401) # => "259769"
57
71
 
58
72
  # OTP verified with a counter
59
- hotp.verify("316439", 1401) # => 1401
60
- hotp.verify("316439", 1402) # => nil
73
+ hotp.verify("259769", 1401) # => 1401
74
+ hotp.verify("259769", 1402) # => nil
61
75
  ```
62
76
 
63
77
  ### Preventing reuse of Time based OTP's
@@ -68,18 +82,21 @@ the interval window (default 30 seconds)
68
82
  The following is an example of this in action:
69
83
 
70
84
  ```ruby
71
- User.find(someUserID)
85
+ user = User.find(someUserID)
72
86
  totp = ROTP::TOTP.new(user.otp_secret)
73
87
  totp.now # => "492039"
74
88
 
89
+ # Let's take a look at the last time the user authenticated with an OTP
75
90
  user.last_otp_at # => 1432703530
76
91
 
77
92
  # Verify the OTP
78
93
  last_otp_at = totp.verify("492039", after: user.last_otp_at) #=> 1472145760
79
94
  # ROTP returns the timestamp(int) of the current period
95
+
80
96
  # Store this on the user's account
81
97
  user.update(last_otp_at: last_otp_at)
82
- # Someone attempts to reused the OTP inside the 30s window
98
+
99
+ # Someone attempts to reuse the OTP inside the 30s window
83
100
  last_otp_at = totp.verify("492039", after: user.last_otp_at) #=> nil
84
101
  # It fails to verify because we are still in the same 30s interval window
85
102
  ```
@@ -104,7 +121,7 @@ totp.verify("250939", drift_behind: 15, at: now + 45) # => nil
104
121
  ### Generating a Base32 Secret key
105
122
 
106
123
  ```ruby
107
- ROTP::Base32.random_base32 # returns a 32 character base32 secret. Compatible with Google Authenticator
124
+ ROTP::Base32.random # returns a 160 bit (32 character) base32 secret. Compatible with Google Authenticator
108
125
  ```
109
126
 
110
127
  Note: The Base32 format conforms to [RFC 4648 Base32](http://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet)
@@ -115,8 +132,11 @@ Provisioning URI's generated by ROTP are compatible with most One Time Password
115
132
  Google Authenticator.
116
133
 
117
134
  ```ruby
118
- totp.provisioning_uri("alice@google.com") # => 'otpauth://totp/issuer:alice@google.com?secret=JBSWY3DPEHPK3PXP'
119
- hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/issuer:alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
135
+ totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service")
136
+ totp.provisioning_uri("alice@google.com") # => 'otpauth://totp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service'
137
+
138
+ hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service")
139
+ hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/alice%40google.com?secret=base32secret3232&counter=0'
120
140
  ```
121
141
 
122
142
  This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application.
@@ -143,9 +163,25 @@ bundle install
143
163
  bundle exec rspec
144
164
  ```
145
165
 
166
+ ### Testing with Docker
167
+
168
+ In order to make it easier to test against different ruby version, ROTP comes
169
+ with a set of Dockerfiles for each version that we test against in Travis
170
+
171
+ ```bash
172
+ docker build -f Dockerfile-2.6 -t rotp_2.6 .
173
+ docker run --rm -v $(pwd):/usr/src/app rotp_2.6
174
+ ```
175
+
176
+ Alternately, you may use docker-compose to run all the tests:
177
+
178
+ ```
179
+ docker-compose up
180
+ ```
181
+
146
182
  ## Executable Usage
147
183
 
148
- The rotp rubygem includes an executable for helping with testing and debugging
184
+ The rotp rubygem includes CLI version to help with testing and debugging
149
185
 
150
186
  ```bash
151
187
  # Try this to get an overview of the commands
@@ -162,7 +198,7 @@ Have a look at the [contributors graph](https://github.com/mdp/rotp/graphs/contr
162
198
 
163
199
  ## License
164
200
 
165
- MIT Copyright (C) 2016 by Mark Percival, see [LICENSE](https://github.com/mdp/rotp/blob/master/LICENSE) for details.
201
+ MIT Copyright (C) 2019 by Mark Percival, see [LICENSE](https://github.com/mdp/rotp/blob/master/LICENSE) for details.
166
202
 
167
203
  ## Other implementations
168
204
 
@@ -0,0 +1,37 @@
1
+ version: "3.8"
2
+ services:
3
+ ruby_2_3:
4
+ build:
5
+ context: .
6
+ dockerfile: Dockerfile-2.3
7
+ volumes:
8
+ - "./lib:/usr/src/app/lib"
9
+ - "./spec:/usr/src/app/spec"
10
+ ruby_2_5:
11
+ build:
12
+ context: .
13
+ dockerfile: Dockerfile-2.5
14
+ volumes:
15
+ - "./lib:/usr/src/app/lib"
16
+ - "./spec:/usr/src/app/spec"
17
+ ruby_2_6:
18
+ build:
19
+ context: .
20
+ dockerfile: Dockerfile-2.6
21
+ volumes:
22
+ - "./lib:/usr/src/app/lib"
23
+ - "./spec:/usr/src/app/spec"
24
+ ruby_2_7:
25
+ build:
26
+ context: .
27
+ dockerfile: Dockerfile-2.7
28
+ volumes:
29
+ - "./lib:/usr/src/app/lib"
30
+ - "./spec:/usr/src/app/spec"
31
+ ruby_3_0_rc:
32
+ build:
33
+ context: .
34
+ dockerfile: Dockerfile-3.0-rc
35
+ volumes:
36
+ - "./lib:/usr/src/app/lib"
37
+ - "./spec:/usr/src/app/spec"
@@ -68,6 +68,10 @@ module ROTP
68
68
  parser.on_tail('-h', '--help', 'Show this message') do
69
69
  options!.mode = :help
70
70
  end
71
+
72
+ parser.on('-d', '--digest [ALGORITHM]', 'Use algorithm for the digest (default sha1)') do |digest|
73
+ options!.digest = digest
74
+ end
71
75
  end
72
76
  end
73
77
 
data/lib/rotp/base32.rb CHANGED
@@ -1,51 +1,75 @@
1
+ require 'securerandom'
2
+
1
3
  module ROTP
2
4
  class Base32
3
5
  class Base32Error < RuntimeError; end
4
- CHARS = 'abcdefghijklmnopqrstuvwxyz234567'.each_char.to_a
6
+ CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'.each_char.to_a
7
+ SHIFT = 5
8
+ MASK = 31
5
9
 
6
10
  class << self
11
+
7
12
  def decode(str)
8
- str = str.tr('=', '')
9
- output = []
10
- str.scan(/.{1,8}/).each do |block|
11
- char_array = decode_block(block).map(&:chr)
12
- output << char_array
13
+ buffer = 0
14
+ idx = 0
15
+ bits_left = 0
16
+ str = str.tr('=', '').upcase
17
+ result = []
18
+ str.split('').each do |char|
19
+ buffer = buffer << SHIFT
20
+ buffer = buffer | (decode_quint(char) & MASK)
21
+ bits_left = bits_left + SHIFT
22
+ if bits_left >= 8
23
+ result[idx] = (buffer >> (bits_left - 8)) & 255
24
+ idx = idx + 1
25
+ bits_left = bits_left - 8
26
+ end
13
27
  end
14
- output.join
28
+ result.pack('c*')
15
29
  end
16
30
 
17
- def random_base32(length = 32)
18
- b32 = ''
19
- SecureRandom.random_bytes(length).each_byte do |b|
20
- b32 << CHARS[b % 32]
31
+ def encode(b)
32
+ data = b.unpack('c*')
33
+ out = String.new
34
+ buffer = data[0]
35
+ idx = 1
36
+ bits_left = 8
37
+ while bits_left > 0 || idx < data.length
38
+ if bits_left < SHIFT
39
+ if idx < data.length
40
+ buffer = buffer << 8
41
+ buffer = buffer | (data[idx] & 255)
42
+ bits_left = bits_left + 8
43
+ idx = idx + 1
44
+ else
45
+ pad = SHIFT - bits_left
46
+ buffer = buffer << pad
47
+ bits_left = bits_left + pad
48
+ end
49
+ end
50
+ val = MASK & (buffer >> (bits_left - SHIFT))
51
+ bits_left = bits_left - SHIFT
52
+ out.concat(CHARS[val])
21
53
  end
22
- b32
54
+ return out
23
55
  end
24
56
 
25
- private
26
-
27
- def decode_block(block)
28
- length = block.scan(/[^=]/).length
29
- quints = block.each_char.map { |c| decode_quint(c) }
30
- bytes = []
31
- bytes[0] = (quints[0] << 3) + (quints[1] ? quints[1] >> 2 : 0)
32
- return bytes if length < 3
33
-
34
- bytes[1] = ((quints[1] & 3) << 6) + (quints[2] << 1) + (quints[3] ? quints[3] >> 4 : 0)
35
- return bytes if length < 4
36
-
37
- bytes[2] = ((quints[3] & 15) << 4) + (quints[4] ? quints[4] >> 1 : 0)
38
- return bytes if length < 6
39
-
40
- bytes[3] = ((quints[4] & 1) << 7) + (quints[5] << 2) + (quints[6] ? quints[6] >> 3 : 0)
41
- return bytes if length < 7
57
+ # Defaults to 160 bit long secret (meaning a 32 character long base32 secret)
58
+ def random(byte_length = 20)
59
+ rand_bytes = SecureRandom.random_bytes(byte_length)
60
+ self.encode(rand_bytes)
61
+ end
42
62
 
43
- bytes[4] = ((quints[6] & 7) << 5) + (quints[7] || 0)
44
- bytes
63
+ # Prevent breaking changes
64
+ def random_base32(str_len = 32)
65
+ byte_length = str_len * 5/8
66
+ random(byte_length)
45
67
  end
46
68
 
69
+ private
70
+
47
71
  def decode_quint(q)
48
- CHARS.index(q.downcase) || raise(Base32Error, "Invalid Base32 Character - '#{q}'")
72
+ CHARS.index(q) || raise(Base32Error, "Invalid Base32 Character - '#{q}'")
49
73
  end
50
74
  end
51
75
  end
data/lib/rotp/cli.rb CHANGED
@@ -19,7 +19,7 @@ module ROTP
19
19
  if %i[time hmac].include?(options.mode)
20
20
  if options.secret.to_s == ''
21
21
  red 'You must also specify a --secret. Try --help for help.'
22
- elsif options.secret.to_s.chars.any? { |c| ROTP::Base32::CHARS.index(c.downcase).nil? }
22
+ elsif options.secret.to_s.chars.any? { |c| ROTP::Base32::CHARS.index(c.upcase).nil? }
23
23
  red 'Secret must be in RFC4648 Base32 format - http://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet'
24
24
  end
25
25
  end
@@ -31,9 +31,9 @@ module ROTP
31
31
  return arguments.to_s if options.mode == :help
32
32
 
33
33
  if options.mode == :time
34
- ROTP::TOTP.new(options.secret).now
34
+ ROTP::TOTP.new(options.secret, options).now
35
35
  elsif options.mode == :hmac
36
- ROTP::HOTP.new(options.secret).at options.counter
36
+ ROTP::HOTP.new(options.secret, options).at options.counter
37
37
  end
38
38
  end
39
39
 
data/lib/rotp/hotp.rb CHANGED
@@ -25,12 +25,7 @@ module ROTP
25
25
  # @param [Integer] initial_count starting counter value, defaults to 0
26
26
  # @return [String] provisioning uri
27
27
  def provisioning_uri(name, initial_count = 0)
28
- params = {
29
- secret: secret,
30
- counter: initial_count,
31
- digits: digits == DEFAULT_DIGITS ? nil : digits
32
- }
33
- encode_params("otpauth://hotp/#{Addressable::URI.escape(name)}", params)
28
+ OTP::URI.new(self, account_name: name, counter: initial_count).to_s
34
29
  end
35
30
  end
36
31
  end
@@ -0,0 +1,79 @@
1
+ module ROTP
2
+ class OTP
3
+ # https://github.com/google/google-authenticator/wiki/Key-Uri-Format
4
+ class URI
5
+ def initialize(otp, account_name:, counter: nil)
6
+ @otp = otp
7
+ @account_name = account_name
8
+ @counter = counter
9
+ end
10
+
11
+ def to_s
12
+ "otpauth://#{type}/#{label}?#{parameters}"
13
+ end
14
+
15
+ private
16
+
17
+ def algorithm
18
+ return unless %w[sha256 sha512].include?(@otp.digest)
19
+
20
+ @otp.digest.upcase
21
+ end
22
+
23
+ def counter
24
+ return if @otp.is_a?(TOTP)
25
+ fail if @counter.nil?
26
+
27
+ @counter
28
+ end
29
+
30
+ def digits
31
+ return if @otp.digits == DEFAULT_DIGITS
32
+
33
+ @otp.digits
34
+ end
35
+
36
+ def issuer
37
+ return if @otp.is_a?(HOTP)
38
+
39
+ @otp.issuer&.strip&.tr(':', '_')
40
+ end
41
+
42
+ def label
43
+ [issuer, @account_name.rstrip]
44
+ .compact
45
+ .map { |s| s.tr(':', '_') }
46
+ .map { |s| ERB::Util.url_encode(s) }
47
+ .join(':')
48
+ end
49
+
50
+ def parameters
51
+ {
52
+ secret: @otp.secret,
53
+ issuer: issuer,
54
+ algorithm: algorithm,
55
+ digits: digits,
56
+ period: period,
57
+ counter: counter,
58
+ }
59
+ .reject { |_, v| v.nil? }
60
+ .map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }
61
+ .join('&')
62
+ end
63
+
64
+ def period
65
+ return if @otp.is_a?(HOTP)
66
+ return if @otp.interval == DEFAULT_INTERVAL
67
+
68
+ @otp.interval
69
+ end
70
+
71
+ def type
72
+ case @otp
73
+ when TOTP then 'totp'
74
+ when HOTP then 'hotp'
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
data/lib/rotp/otp.rb CHANGED
@@ -5,10 +5,10 @@ module ROTP
5
5
 
6
6
  # @param [String] secret in the form of base32
7
7
  # @option options digits [Integer] (6)
8
- # Number of integers in the OTP
8
+ # Number of integers in the OTP.
9
9
  # Google Authenticate only supports 6 currently
10
10
  # @option options digest [String] (sha1)
11
- # Digest used in the HMAC
11
+ # Digest used in the HMAC.
12
12
  # Google Authenticate only supports 'sha1' currently
13
13
  # @returns [OTP] OTP instantiation
14
14
  def initialize(s, options = {})
@@ -66,16 +66,6 @@ module ROTP
66
66
  result.reverse.join.rjust(padding, 0.chr)
67
67
  end
68
68
 
69
- # A very simple param encoder
70
- def encode_params(uri, params)
71
- params_str = String.new('?')
72
- params.each do |k, v|
73
- params_str << "#{k}=#{CGI.escape(v.to_s)}&" if v
74
- end
75
- params_str.chop!
76
- uri + params_str
77
- end
78
-
79
69
  # constant-time compare the strings
80
70
  def time_constant_compare(a, b)
81
71
  return false if a.empty? || b.empty? || a.bytesize != b.bytesize
data/lib/rotp/totp.rb CHANGED
@@ -54,19 +54,7 @@ module ROTP
54
54
  # @param [String] name of the account
55
55
  # @return [String] provisioning URI
56
56
  def provisioning_uri(name)
57
- # The format of this URI is documented at:
58
- # https://github.com/google/google-authenticator/wiki/Key-Uri-Format
59
- # For compatibility the issuer appears both before that account name and also in the
60
- # query string.
61
- issuer_string = issuer.nil? ? '' : "#{Addressable::URI.escape(issuer)}:"
62
- params = {
63
- secret: secret,
64
- period: interval == 30 ? nil : interval,
65
- issuer: issuer,
66
- digits: digits == DEFAULT_DIGITS ? nil : digits,
67
- algorithm: digest.casecmp('SHA1').zero? ? nil : digest.upcase
68
- }
69
- encode_params("otpauth://totp/#{issuer_string}#{Addressable::URI.escape(name)}", params)
57
+ OTP::URI.new(self, account_name: name).to_s
70
58
  end
71
59
 
72
60
  private
data/lib/rotp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ROTP
2
- VERSION = '4.0.2'.freeze
2
+ VERSION = '6.2.0'.freeze
3
3
  end
data/lib/rotp.rb CHANGED
@@ -1,9 +1,8 @@
1
- require 'cgi'
2
- require 'addressable'
3
- require 'securerandom'
4
1
  require 'openssl'
2
+ require 'erb'
5
3
  require 'rotp/base32'
6
4
  require 'rotp/otp'
5
+ require 'rotp/otp/uri'
7
6
  require 'rotp/hotp'
8
7
  require 'rotp/totp'
9
8
 
data/rotp.gemspec CHANGED
@@ -4,23 +4,20 @@ Gem::Specification.new do |s|
4
4
  s.name = 'rotp'
5
5
  s.version = ROTP::VERSION
6
6
  s.platform = Gem::Platform::RUBY
7
+ s.required_ruby_version = '>= 2.3'
7
8
  s.license = 'MIT'
8
9
  s.authors = ['Mark Percival']
9
10
  s.email = ['mark@markpercival.us']
10
- s.homepage = 'http://github.com/mdp/rotp'
11
+ s.homepage = 'https://github.com/mdp/rotp'
11
12
  s.summary = 'A Ruby library for generating and verifying one time passwords'
12
13
  s.description = 'Works for both HOTP and TOTP, and includes QR Code provisioning'
13
14
 
14
- s.rubyforge_project = 'rotp'
15
-
16
15
  s.files = `git ls-files`.split("\n")
17
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
17
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
18
  s.require_paths = ['lib']
20
19
 
21
- s.add_runtime_dependency 'addressable', '~> 2.5'
22
-
23
- s.add_development_dependency 'rake', '~> 10.5'
20
+ s.add_development_dependency "rake", "~> 13.0"
24
21
  s.add_development_dependency 'rspec', '~> 3.5'
25
22
  s.add_development_dependency 'simplecov', '~> 0.12'
26
23
  s.add_development_dependency 'timecop', '~> 0.8'
@@ -1,24 +1,33 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe ROTP::Base32 do
4
- describe '.random_base32' do
4
+ describe '.random' do
5
5
  context 'without arguments' do
6
- let(:base32) { ROTP::Base32.random_base32 }
6
+ let(:base32) { ROTP::Base32.random }
7
7
 
8
- it 'is 32 characters long' do
8
+ it 'is 20 bytes (160 bits) long (resulting in a 32 character base32 code)' do
9
+ expect(ROTP::Base32.decode(base32).length).to eq 20
9
10
  expect(base32.length).to eq 32
10
11
  end
11
12
 
12
13
  it 'is base32 charset' do
13
- expect(base32).to match(/\A[a-z2-7]+\z/)
14
+ expect(base32).to match(/\A[A-Z2-7]+\z/)
14
15
  end
15
16
  end
16
17
 
17
18
  context 'with arguments' do
18
- let(:base32) { ROTP::Base32.random_base32 32 }
19
+ let(:base32) { ROTP::Base32.random 48 }
19
20
 
20
- it 'allows a specific length' do
21
- expect(base32.length).to eq 32
21
+ it 'returns the appropriate byte length code' do
22
+ expect(ROTP::Base32.decode(base32).length).to eq 48
23
+ end
24
+ end
25
+
26
+ context 'alias to older random_base32' do
27
+ let(:base32) { ROTP::Base32.random_base32(36) }
28
+ it 'is base32 charset' do
29
+ expect(base32.length).to eq 36
30
+ expect(ROTP::Base32.decode(base32).length).to eq 22
22
31
  end
23
32
  end
24
33
  end
@@ -33,22 +42,40 @@ RSpec.describe ROTP::Base32 do
33
42
 
34
43
  context 'valid input data' do
35
44
  it 'correctly decodes a string' do
36
- expect(ROTP::Base32.decode('F').unpack('H*').first).to eq '28'
37
- expect(ROTP::Base32.decode('23').unpack('H*').first).to eq 'd6'
38
- expect(ROTP::Base32.decode('234').unpack('H*').first).to eq 'd6f8'
39
- expect(ROTP::Base32.decode('234A').unpack('H*').first).to eq 'd6f800'
40
- expect(ROTP::Base32.decode('234B').unpack('H*').first).to eq 'd6f810'
41
- expect(ROTP::Base32.decode('234BCD').unpack('H*').first).to eq 'd6f8110c'
42
- expect(ROTP::Base32.decode('234BCDE').unpack('H*').first).to eq 'd6f8110c80'
43
- expect(ROTP::Base32.decode('234BCDEFG').unpack('H*').first).to eq 'd6f8110c8530'
44
- expect(ROTP::Base32.decode('234BCDEFG234BCDEFG').unpack('H*').first).to eq 'd6f8110c8536b7c0886429'
45
+ expect(ROTP::Base32.decode('2EB7C66WC5TSO').unpack('H*').first).to eq 'd103f17bd6176727'
46
+ expect(ROTP::Base32.decode('Y6Y5ZCAC7NABCHSJ').unpack('H*').first).to eq 'c7b1dc8802fb40111e49'
47
+ end
48
+
49
+ it 'correctly decode strings with trailing bits (not a multiple of 8)' do
50
+ # Dropbox style 26 characters (26*5==130 bits or 16.25 bytes, but chopped to 128)
51
+ # Matches the behavior of Google Authenticator, drops extra 2 empty bits
52
+ expect(ROTP::Base32.decode('YVT6Z2XF4BQJNBMTD7M6QBQCEM').unpack('H*').first).to eq 'c567eceae5e0609685931fd9e8060223'
53
+
54
+ # For completeness, test all the possibilities allowed by Google Authenticator
55
+ # Drop the incomplete empty extra 4 bits (28*5==140bits or 17.5 bytes, chopped to 136 bits)
56
+ expect(ROTP::Base32.decode('5GGZQB3WN6LD7V3L5HPDYTQUANEQ').unpack('H*').first).to eq 'e98d9807766f963fd76be9de3c4e140349'
57
+
45
58
  end
46
59
 
47
60
  context 'with padding' do
48
61
  it 'correctly decodes a string' do
49
- expect(ROTP::Base32.decode('F==').unpack('H*').first).to eq '28'
62
+ expect(ROTP::Base32.decode('234A===').unpack('H*').first).to eq 'd6f8'
50
63
  end
51
64
  end
65
+
66
+ end
67
+ end
68
+
69
+ describe '.encode' do
70
+ context 'encode input data' do
71
+ it 'correctly encodes data' do
72
+ expect(ROTP::Base32.encode(hex_to_bin('3c204da94294ff82103ee34e96f74b48'))).to eq 'HQQE3KKCST7YEEB64NHJN52LJA'
73
+ end
52
74
  end
53
75
  end
76
+
77
+ end
78
+
79
+ def hex_to_bin(s)
80
+ s.scan(/../).map { |x| x.hex }.pack('c*')
54
81
  end
@@ -18,6 +18,14 @@ RSpec.describe ROTP::CLI do
18
18
  end
19
19
  end
20
20
 
21
+ context 'generating a TOTP with sha256 digest' do
22
+ let(:argv) { %w[--secret JBSWY3DPEHPK3PXP --digest sha256] }
23
+
24
+ it 'prints the corresponding token' do
25
+ expect(output).to eq '544902'
26
+ end
27
+ end
28
+
21
29
  context 'generating a TOTP with no secret' do
22
30
  let(:argv) { %w[--time --secret] }
23
31
 
@@ -49,4 +57,12 @@ RSpec.describe ROTP::CLI do
49
57
  expect(output).to eq '161024'
50
58
  end
51
59
  end
60
+
61
+ context 'generating a HOTP' do
62
+ let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234 --digest sha256] }
63
+
64
+ it 'prints the corresponding token' do
65
+ expect(output).to eq '325941'
66
+ end
67
+ end
52
68
  end
@@ -108,29 +108,14 @@ RSpec.describe ROTP::HOTP do
108
108
  end
109
109
 
110
110
  describe '#provisioning_uri' do
111
- let(:uri) { hotp.provisioning_uri('mark@percival') }
112
- let(:params) { CGI.parse URI.parse(uri).query }
113
-
114
- it 'has the correct format' do
115
- expect(uri).to match %r{\Aotpauth:\/\/hotp.+}
116
- end
117
-
118
- it 'includes the secret as parameter' do
119
- expect(params['secret'].first).to eq 'a' * 32
120
- end
121
-
122
- context 'with default digits' do
123
- it 'does not include digits parameter with default digits' do
124
- expect(params['digits'].first).to be_nil
125
- end
111
+ it 'accepts the account name' do
112
+ expect(hotp.provisioning_uri('mark@percival'))
113
+ .to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
126
114
  end
127
115
 
128
- context 'with non-default digits' do
129
- let(:hotp) { ROTP::HOTP.new('a' * 32, digits: 8) }
130
-
131
- it 'includes digits parameter' do
132
- expect(params['digits'].first).to eq '8'
133
- end
116
+ it 'also accepts a custom counter value' do
117
+ expect(hotp.provisioning_uri('mark@percival', 17))
118
+ .to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17'
134
119
  end
135
120
  end
136
121
  end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROTP::OTP::URI do
4
+ it 'meets basic functionality' do
5
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
6
+ uri = described_class.new(otp, account_name: 'alice@google.com')
7
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP'
8
+ end
9
+
10
+ it 'includes issuer' do
11
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Example')
12
+ uri = described_class.new(otp, account_name: 'alice@google.com')
13
+ expect(uri.to_s).to eq 'otpauth://totp/Example:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example'
14
+ end
15
+
16
+ it 'encodes the account name' do
17
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Provider1')
18
+ uri = described_class.new(otp, account_name: 'Alice Smith')
19
+ expect(uri.to_s).to eq 'otpauth://totp/Provider1:Alice%20Smith?secret=JBSWY3DPEHPK3PXP&issuer=Provider1'
20
+ end
21
+
22
+ it 'encodes the issuer' do
23
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big Corporation')
24
+ uri = described_class.new(otp, account_name: ' alice@bigco.com')
25
+ expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation'
26
+ end
27
+
28
+ it 'includes non-default SHA256 algorithm' do
29
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha256')
30
+ uri = described_class.new(otp, account_name: 'alice@google.com')
31
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA256'
32
+ end
33
+
34
+ it 'includes non-default SHA512 algorithm' do
35
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha512')
36
+ uri = described_class.new(otp, account_name: 'alice@google.com')
37
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA512'
38
+ end
39
+
40
+ it 'includes non-default 8 digits' do
41
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 8)
42
+ uri = described_class.new(otp, account_name: 'alice@google.com')
43
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&digits=8'
44
+ end
45
+
46
+ it 'includes non-default period for TOTP' do
47
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', interval: 35)
48
+ uri = described_class.new(otp, account_name: 'alice@google.com')
49
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&period=35'
50
+ end
51
+
52
+ it 'includes non-default counter for HOTP' do
53
+ otp = ROTP::HOTP.new('JBSWY3DPEHPK3PXP')
54
+ uri = described_class.new(otp, account_name: 'alice@google.com', counter: 17)
55
+ expect(uri.to_s).to eq 'otpauth://hotp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&counter=17'
56
+ end
57
+
58
+ it 'can include all parameters' do
59
+ otp = ROTP::TOTP.new(
60
+ 'HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ',
61
+ digest: 'sha512',
62
+ digits: 8,
63
+ interval: 60,
64
+ issuer: 'ACME Co',
65
+ )
66
+ uri = described_class.new(otp, account_name: 'john.doe@email.com')
67
+ expect(uri.to_s).to eq'otpauth://totp/ACME%20Co:john.doe%40email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA512&digits=8&period=60'
68
+ end
69
+
70
+ it 'strips leading and trailing whitespace from the issuer' do
71
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: ' Big Corporation ')
72
+ uri = described_class.new(otp, account_name: ' alice@bigco.com')
73
+ expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation'
74
+ end
75
+
76
+ it 'strips trailing whitespace from the account name' do
77
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
78
+ uri = described_class.new(otp, account_name: ' alice@google.com ')
79
+ expect(uri.to_s).to eq 'otpauth://totp/%20%20alice%40google.com?secret=JBSWY3DPEHPK3PXP'
80
+ end
81
+
82
+ it 'replaces colons in the issuer with underscores' do
83
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big:Corporation')
84
+ uri = described_class.new(otp, account_name: 'alice@bigco.com')
85
+ expect(uri.to_s).to eq 'otpauth://totp/Big_Corporation:alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big_Corporation'
86
+ end
87
+
88
+ it 'replaces colons in the account name with underscores' do
89
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
90
+ uri = described_class.new(otp, account_name: 'Alice:Smith')
91
+ expect(uri.to_s).to eq 'otpauth://totp/Alice_Smith?secret=JBSWY3DPEHPK3PXP'
92
+ end
93
+
94
+ it 'handles email account names with sub-addressing' do
95
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
96
+ uri = described_class.new(otp, account_name: 'alice+1234@google.com')
97
+ expect(uri.to_s).to eq 'otpauth://totp/alice%2B1234%40google.com?secret=JBSWY3DPEHPK3PXP'
98
+ end
99
+ end
@@ -221,79 +221,9 @@ RSpec.describe ROTP::TOTP do
221
221
  end
222
222
 
223
223
  describe '#provisioning_uri' do
224
- let(:uri) { totp.provisioning_uri('mark@percival') }
225
- let(:params) { CGI.parse URI.parse(uri).query }
226
-
227
- context 'without issuer' do
228
- it 'has the correct format' do
229
- expect(uri).to match %r{\Aotpauth:\/\/totp.+}
230
- end
231
-
232
- it 'includes the secret as parameter' do
233
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
234
- end
235
- end
236
-
237
- context 'with default digits' do
238
- it 'does does not include digits parameter' do
239
- expect(params['digits'].first).to be_nil
240
- end
241
- end
242
-
243
- context 'with non-default digits' do
244
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digits: 8 }
245
-
246
- it 'does does not include digits parameter' do
247
- expect(params['digits'].first).to eq '8'
248
- end
249
- end
250
-
251
- context 'with issuer' do
252
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', issuer: 'FooCo' }
253
-
254
- it 'has the correct format' do
255
- expect(uri).to match %r{\Aotpauth:\/\/totp/FooCo:.+}
256
- end
257
-
258
- it 'includes the secret as parameter' do
259
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
260
- end
261
-
262
- it 'includes the issuer as parameter' do
263
- expect(params['issuer'].first).to eq 'FooCo'
264
- end
265
- end
266
-
267
- context 'with custom interval' do
268
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', interval: 60 }
269
-
270
- it 'has the correct format' do
271
- expect(uri).to match %r{\Aotpauth:\/\/totp.+}
272
- end
273
-
274
- it 'includes the secret as parameter' do
275
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
276
- end
277
-
278
- it 'includes the interval as period parameter' do
279
- expect(params['period'].first).to eq '60'
280
- end
281
- end
282
-
283
- context 'with custom digest' do
284
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digest: 'sha256' }
285
-
286
- it 'has the correct format' do
287
- expect(uri).to match %r{\Aotpauth:\/\/totp.+}
288
- end
289
-
290
- it 'includes the secret as parameter' do
291
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
292
- end
293
-
294
- it 'includes the digest as algorithm parameter' do
295
- expect(params['algorithm'].first).to eq 'SHA256'
296
- end
224
+ it 'accepts the account name' do
225
+ expect(totp.provisioning_uri('mark@percival'))
226
+ .to eq 'otpauth://totp/mark%40percival?secret=JBSWY3DPEHPK3PXP'
297
227
  end
298
228
  end
299
229
 
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotp
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 6.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Percival
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-01 00:00:00.000000000 Z
11
+ date: 2020-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: addressable
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.5'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.5'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rake
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
17
  - - "~>"
32
18
  - !ruby/object:Gem::Version
33
- version: '10.5'
19
+ version: '13.0'
34
20
  type: :development
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
24
  - - "~>"
39
25
  - !ruby/object:Gem::Version
40
- version: '10.5'
26
+ version: '13.0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rspec
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -92,14 +78,15 @@ files:
92
78
  - ".gitignore"
93
79
  - ".travis.yml"
94
80
  - CHANGELOG.md
95
- - Dockerfile-1.9
96
- - Dockerfile-2.1
97
81
  - Dockerfile-2.3
82
+ - Dockerfile-2.5
83
+ - Dockerfile-2.6
84
+ - Dockerfile-2.7
85
+ - Dockerfile-3.0-rc
98
86
  - Gemfile
99
87
  - Guardfile
100
88
  - LICENSE
101
89
  - README.md
102
- - Rakefile
103
90
  - bin/rotp
104
91
  - doc/ROTP/HOTP.html
105
92
  - doc/ROTP/OTP.html
@@ -119,12 +106,14 @@ files:
119
106
  - doc/js/jquery.js
120
107
  - doc/method_list.html
121
108
  - doc/top-level-namespace.html
109
+ - docker-compose.yml
122
110
  - lib/rotp.rb
123
111
  - lib/rotp/arguments.rb
124
112
  - lib/rotp/base32.rb
125
113
  - lib/rotp/cli.rb
126
114
  - lib/rotp/hotp.rb
127
115
  - lib/rotp/otp.rb
116
+ - lib/rotp/otp/uri.rb
128
117
  - lib/rotp/totp.rb
129
118
  - lib/rotp/version.rb
130
119
  - rotp.gemspec
@@ -132,9 +121,10 @@ files:
132
121
  - spec/lib/rotp/base32_spec.rb
133
122
  - spec/lib/rotp/cli_spec.rb
134
123
  - spec/lib/rotp/hotp_spec.rb
124
+ - spec/lib/rotp/otp/uri_spec.rb
135
125
  - spec/lib/rotp/totp_spec.rb
136
126
  - spec/spec_helper.rb
137
- homepage: http://github.com/mdp/rotp
127
+ homepage: https://github.com/mdp/rotp
138
128
  licenses:
139
129
  - MIT
140
130
  metadata: {}
@@ -146,22 +136,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
146
136
  requirements:
147
137
  - - ">="
148
138
  - !ruby/object:Gem::Version
149
- version: '0'
139
+ version: '2.3'
150
140
  required_rubygems_version: !ruby/object:Gem::Requirement
151
141
  requirements:
152
142
  - - ">="
153
143
  - !ruby/object:Gem::Version
154
144
  version: '0'
155
145
  requirements: []
156
- rubyforge_project: rotp
157
- rubygems_version: 2.7.6
146
+ rubygems_version: 3.1.2
158
147
  signing_key:
159
148
  specification_version: 4
160
149
  summary: A Ruby library for generating and verifying one time passwords
161
- test_files:
162
- - spec/lib/rotp/arguments_spec.rb
163
- - spec/lib/rotp/base32_spec.rb
164
- - spec/lib/rotp/cli_spec.rb
165
- - spec/lib/rotp/hotp_spec.rb
166
- - spec/lib/rotp/totp_spec.rb
167
- - spec/spec_helper.rb
150
+ test_files: []
data/Dockerfile-1.9 DELETED
@@ -1,15 +0,0 @@
1
- FROM ruby:1.9
2
-
3
- # throw errors if Gemfile has been modified since Gemfile.lock
4
- RUN bundle config --global frozen 1
5
-
6
- RUN mkdir -p /usr/src/app
7
- WORKDIR /usr/src/app
8
-
9
- COPY Gemfile /usr/src/app/
10
- COPY Gemfile.lock /usr/src/app/
11
- COPY . /usr/src/app
12
- RUN bundle install
13
-
14
- CMD ["bundler", "exec", "rspec"]
15
-
data/Dockerfile-2.1 DELETED
@@ -1,16 +0,0 @@
1
- FROM ruby:2.1
2
-
3
- # throw errors if Gemfile has been modified since Gemfile.lock
4
- RUN bundle config --global frozen 1
5
-
6
- RUN mkdir -p /usr/src/app
7
- WORKDIR /usr/src/app
8
-
9
- COPY Gemfile /usr/src/app/
10
- COPY Gemfile.lock /usr/src/app/
11
- COPY . /usr/src/app
12
- RUN bundle install
13
-
14
-
15
- CMD ["bundler", "exec", "rspec"]
16
-
data/Rakefile DELETED
@@ -1,9 +0,0 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
3
- require 'rspec/core/rake_task'
4
-
5
- RSpec::Core::RakeTask.new(:rspec) do |spec|
6
- spec.pattern = 'spec/**/*_spec.rb'
7
- end
8
-
9
- task default: :rspec