rotp-6.2.0/0000755000175100017510000000000013753334422011537 5ustar pravipravirotp-6.2.0/docker-compose.yml0000644000175100017510000000144113753334422015174 0ustar pravipraviversion: "3.8" services: ruby_2_3: build: context: . dockerfile: Dockerfile-2.3 volumes: - "./lib:/usr/src/app/lib" - "./spec:/usr/src/app/spec" ruby_2_5: build: context: . dockerfile: Dockerfile-2.5 volumes: - "./lib:/usr/src/app/lib" - "./spec:/usr/src/app/spec" ruby_2_6: build: context: . dockerfile: Dockerfile-2.6 volumes: - "./lib:/usr/src/app/lib" - "./spec:/usr/src/app/spec" ruby_2_7: build: context: . dockerfile: Dockerfile-2.7 volumes: - "./lib:/usr/src/app/lib" - "./spec:/usr/src/app/spec" ruby_3_0_rc: build: context: . dockerfile: Dockerfile-3.0-rc volumes: - "./lib:/usr/src/app/lib" - "./spec:/usr/src/app/spec" rotp-6.2.0/rotp.gemspec0000644000175100017510000000167013753334422014074 0ustar pravipravirequire './lib/rotp/version' Gem::Specification.new do |s| s.name = 'rotp' s.version = ROTP::VERSION s.platform = Gem::Platform::RUBY s.required_ruby_version = '>= 2.3' s.license = 'MIT' s.authors = ['Mark Percival'] s.email = ['mark@markpercival.us'] s.homepage = 'https://github.com/mdp/rotp' s.summary = 'A Ruby library for generating and verifying one time passwords' s.description = 'Works for both HOTP and TOTP, and includes QR Code provisioning' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } s.require_paths = ['lib'] s.add_development_dependency "rake", "~> 13.0" s.add_development_dependency 'rspec', '~> 3.5' s.add_development_dependency 'simplecov', '~> 0.12' s.add_development_dependency 'timecop', '~> 0.8' end rotp-6.2.0/spec/0000755000175100017510000000000013753334422012471 5ustar pravipravirotp-6.2.0/spec/spec_helper.rb0000644000175100017510000000051313753334422015306 0ustar pravipravirequire 'simplecov' SimpleCov.start do add_filter '/spec/' end require 'rotp' require 'timecop' RSpec.configure do |config| config.disable_monkey_patching! config.raise_errors_for_deprecations! config.color = true config.fail_fast = true config.before do Timecop.return end end require_relative '../lib/rotp' rotp-6.2.0/spec/lib/0000755000175100017510000000000013753334422013237 5ustar pravipravirotp-6.2.0/spec/lib/rotp/0000755000175100017510000000000013753334422014223 5ustar pravipravirotp-6.2.0/spec/lib/rotp/otp/0000755000175100017510000000000013753334422015025 5ustar pravipravirotp-6.2.0/spec/lib/rotp/otp/uri_spec.rb0000644000175100017510000001064013753334422017164 0ustar pravipravirequire 'spec_helper' RSpec.describe ROTP::OTP::URI do it 'meets basic functionality' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP') uri = described_class.new(otp, account_name: 'alice@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP' end it 'includes issuer' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Example') uri = described_class.new(otp, account_name: 'alice@google.com') expect(uri.to_s).to eq 'otpauth://totp/Example:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example' end it 'encodes the account name' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Provider1') uri = described_class.new(otp, account_name: 'Alice Smith') expect(uri.to_s).to eq 'otpauth://totp/Provider1:Alice%20Smith?secret=JBSWY3DPEHPK3PXP&issuer=Provider1' end it 'encodes the issuer' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big Corporation') uri = described_class.new(otp, account_name: ' alice@bigco.com') expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation' end it 'includes non-default SHA256 algorithm' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha256') uri = described_class.new(otp, account_name: 'alice@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA256' end it 'includes non-default SHA512 algorithm' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha512') uri = described_class.new(otp, account_name: 'alice@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA512' end it 'includes non-default 8 digits' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 8) uri = described_class.new(otp, account_name: 'alice@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&digits=8' end it 'includes non-default period for TOTP' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', interval: 35) uri = described_class.new(otp, account_name: 'alice@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&period=35' end it 'includes non-default counter for HOTP' do otp = ROTP::HOTP.new('JBSWY3DPEHPK3PXP') uri = described_class.new(otp, account_name: 'alice@google.com', counter: 17) expect(uri.to_s).to eq 'otpauth://hotp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&counter=17' end it 'can include all parameters' do otp = ROTP::TOTP.new( 'HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ', digest: 'sha512', digits: 8, interval: 60, issuer: 'ACME Co', ) uri = described_class.new(otp, account_name: 'john.doe@email.com') 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' end it 'strips leading and trailing whitespace from the issuer' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: ' Big Corporation ') uri = described_class.new(otp, account_name: ' alice@bigco.com') expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation' end it 'strips trailing whitespace from the account name' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP') uri = described_class.new(otp, account_name: ' alice@google.com ') expect(uri.to_s).to eq 'otpauth://totp/%20%20alice%40google.com?secret=JBSWY3DPEHPK3PXP' end it 'replaces colons in the issuer with underscores' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big:Corporation') uri = described_class.new(otp, account_name: 'alice@bigco.com') expect(uri.to_s).to eq 'otpauth://totp/Big_Corporation:alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big_Corporation' end it 'replaces colons in the account name with underscores' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP') uri = described_class.new(otp, account_name: 'Alice:Smith') expect(uri.to_s).to eq 'otpauth://totp/Alice_Smith?secret=JBSWY3DPEHPK3PXP' end it 'handles email account names with sub-addressing' do otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP') uri = described_class.new(otp, account_name: 'alice+1234@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%2B1234%40google.com?secret=JBSWY3DPEHPK3PXP' end end rotp-6.2.0/spec/lib/rotp/hotp_spec.rb0000644000175100017510000000622513753334422016541 0ustar pravipravirequire 'spec_helper' RSpec.describe ROTP::HOTP do let(:counter) { 1234 } let(:token) { '161024' } let(:hotp) { ROTP::HOTP.new('a' * 32) } describe '#at' do let(:token) { hotp.at counter } context 'only the counter as argument' do it 'generates a string OTP' do expect(token).to eq '161024' end end context 'invalid counter' do it 'raises an error' do expect { hotp.at(-123_456) }.to raise_error(ArgumentError) end end context 'RFC compatibility' do let(:hotp) { ROTP::HOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') } it 'matches the RFC documentation examples' do # 12345678901234567890 in Base32 # GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ expect(hotp.at(0)).to eq '755224' expect(hotp.at(1)).to eq '287082' expect(hotp.at(2)).to eq '359152' expect(hotp.at(3)).to eq '969429' expect(hotp.at(4)).to eq '338314' expect(hotp.at(5)).to eq '254676' expect(hotp.at(6)).to eq '287922' expect(hotp.at(7)).to eq '162583' expect(hotp.at(8)).to eq '399871' expect(hotp.at(9)).to eq '520489' end end end describe '#verify' do let(:verification) { hotp.verify token, counter } context 'numeric token' do let(:token) { 161_024 } it 'raises an error' do expect { verification }.to raise_error(ArgumentError) end end context 'string token' do it 'is true' do expect(verification).to be_truthy end end context 'RFC compatibility' do let(:hotp) { ROTP::HOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') } let(:token) { '520489' } it 'verifies and does not allow reuse' do expect(hotp.verify(token, 9)).to be_truthy expect(hotp.verify(token, 10)).to be_falsey end end describe 'with retries' do let(:verification) { hotp.verify token, counter, retries: retries } context 'counter outside than retries' do let(:counter) { 1223 } let(:retries) { 10 } it 'is false' do expect(verification).to be_falsey end end context 'counter exactly in retry range' do let(:counter) { 1224 } let(:retries) { 10 } it 'is true' do expect(verification).to eq 1234 end end context 'counter in retry range' do let(:counter) { 1224 } let(:retries) { 11 } it 'is true' do expect(verification).to eq 1234 end end context 'counter ahead of token' do let(:counter) { 1235 } let(:retries) { 3 } it 'is false' do expect(verification).to be_falsey end end end end describe '#provisioning_uri' do it 'accepts the account name' do expect(hotp.provisioning_uri('mark@percival')) .to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0' end it 'also accepts a custom counter value' do expect(hotp.provisioning_uri('mark@percival', 17)) .to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17' end end end rotp-6.2.0/spec/lib/rotp/base32_spec.rb0000644000175100017510000000516713753334422016652 0ustar pravipravirequire 'spec_helper' RSpec.describe ROTP::Base32 do describe '.random' do context 'without arguments' do let(:base32) { ROTP::Base32.random } it 'is 20 bytes (160 bits) long (resulting in a 32 character base32 code)' do expect(ROTP::Base32.decode(base32).length).to eq 20 expect(base32.length).to eq 32 end it 'is base32 charset' do expect(base32).to match(/\A[A-Z2-7]+\z/) end end context 'with arguments' do let(:base32) { ROTP::Base32.random 48 } it 'returns the appropriate byte length code' do expect(ROTP::Base32.decode(base32).length).to eq 48 end end context 'alias to older random_base32' do let(:base32) { ROTP::Base32.random_base32(36) } it 'is base32 charset' do expect(base32.length).to eq 36 expect(ROTP::Base32.decode(base32).length).to eq 22 end end end describe '.decode' do context 'corrupt input data' do it 'raises a sane error' do expect { ROTP::Base32.decode('4BCDEFG234BCDEF1') }.to \ raise_error(ROTP::Base32::Base32Error, "Invalid Base32 Character - '1'") end end context 'valid input data' do it 'correctly decodes a string' do expect(ROTP::Base32.decode('2EB7C66WC5TSO').unpack('H*').first).to eq 'd103f17bd6176727' expect(ROTP::Base32.decode('Y6Y5ZCAC7NABCHSJ').unpack('H*').first).to eq 'c7b1dc8802fb40111e49' end it 'correctly decode strings with trailing bits (not a multiple of 8)' do # Dropbox style 26 characters (26*5==130 bits or 16.25 bytes, but chopped to 128) # Matches the behavior of Google Authenticator, drops extra 2 empty bits expect(ROTP::Base32.decode('YVT6Z2XF4BQJNBMTD7M6QBQCEM').unpack('H*').first).to eq 'c567eceae5e0609685931fd9e8060223' # For completeness, test all the possibilities allowed by Google Authenticator # Drop the incomplete empty extra 4 bits (28*5==140bits or 17.5 bytes, chopped to 136 bits) expect(ROTP::Base32.decode('5GGZQB3WN6LD7V3L5HPDYTQUANEQ').unpack('H*').first).to eq 'e98d9807766f963fd76be9de3c4e140349' end context 'with padding' do it 'correctly decodes a string' do expect(ROTP::Base32.decode('234A===').unpack('H*').first).to eq 'd6f8' end end end end describe '.encode' do context 'encode input data' do it 'correctly encodes data' do expect(ROTP::Base32.encode(hex_to_bin('3c204da94294ff82103ee34e96f74b48'))).to eq 'HQQE3KKCST7YEEB64NHJN52LJA' end end end end def hex_to_bin(s) s.scan(/../).map { |x| x.hex }.pack('c*') end rotp-6.2.0/spec/lib/rotp/cli_spec.rb0000644000175100017510000000325513753334422016336 0ustar pravipravirequire 'spec_helper' require 'rotp/cli' RSpec.describe ROTP::CLI do let(:cli) { described_class.new('executable', argv) } let(:output) { cli.output } let(:now) { Time.utc 2012, 1, 1 } before do Timecop.freeze now end context 'generating a TOTP' do let(:argv) { %w[--secret JBSWY3DPEHPK3PXP] } it 'prints the corresponding token' do expect(output).to eq '068212' end end context 'generating a TOTP with sha256 digest' do let(:argv) { %w[--secret JBSWY3DPEHPK3PXP --digest sha256] } it 'prints the corresponding token' do expect(output).to eq '544902' end end context 'generating a TOTP with no secret' do let(:argv) { %w[--time --secret] } it 'prints the corresponding token' do expect(output).to match 'You must also specify a --secret' end end context 'generating a TOTP with bad base32 secret' do let(:argv) { %W[--time --secret #{'1' * 32}] } it 'prints the corresponding token' do expect(output).to match 'Secret must be in RFC4648 Base32 format' end end context 'trying to generate an unsupport type' do let(:argv) { %W[--notreal --secret #{'a' * 32}] } it 'prints the corresponding token' do expect(output).to match 'invalid option: --notreal' end end context 'generating a HOTP' do let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234] } it 'prints the corresponding token' do expect(output).to eq '161024' end end context 'generating a HOTP' do let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234 --digest sha256] } it 'prints the corresponding token' do expect(output).to eq '325941' end end end rotp-6.2.0/spec/lib/rotp/totp_spec.rb0000644000175100017510000001664713753334422016566 0ustar pravipravirequire 'spec_helper' TEST_TIME = Time.utc 2016, 9, 23, 9 # 2016-09-23 09:00:00 UTC TEST_TOKEN = '082630'.freeze RSpec.describe ROTP::TOTP do let(:now) { TEST_TIME } let(:token) { TEST_TOKEN } let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP' } describe '#at' do let(:token) { totp.at now } it 'is a string number' do expect(token).to eq TEST_TOKEN end context 'RFC compatibility' do let(:totp) { ROTP::TOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') } it 'matches the RFC documentation examples' do expect(totp.at(1_111_111_111)).to eq '050471' expect(totp.at(1_234_567_890)).to eq '005924' expect(totp.at(2_000_000_000)).to eq '279037' end end end describe '#verify' do let(:verification) { totp.verify token, at: now } context 'numeric token' do let(:token) { 82_630 } it 'raises an error with an integer' do expect { verification }.to raise_error(ArgumentError) end end context 'unpadded string token' do let(:token) { '82630' } it 'fails to verify' do expect(verification).to be_falsey end end context 'correctly padded string token' do it 'verifies' do expect(verification).to be_truthy end end context 'RFC compatibility' do let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' } before do Timecop.freeze now end context 'correct time based OTP' do let(:token) { '102705' } let(:now) { Time.at 1_297_553_958 } it 'verifies' do expect(totp.verify('102705')).to be_truthy end end context 'wrong time based OTP' do it 'fails to verify' do expect(totp.verify('102705')).to be_falsey end end end context 'invalidating reused tokens' do let(:verification) do totp.verify token, after: after, at: now end let(:after) { nil } context 'passing in the `after` timestamp' do let(:after) do totp.verify TEST_TOKEN, after: nil, at: now end it 'returns a timecode' do expect(after).to be_kind_of(Integer) expect(after).to be_within(30).of(now.to_i) end context 'reusing same token' do it 'is false' do expect(verification).to be_falsy end end end end end def get_timecodes(at, b, a) # Test the private method totp.send('get_timecodes', at, b, a) end describe 'drifting timecodes' do it 'should get timecodes behind' do expect(get_timecodes(TEST_TIME + 15, 15, 0)).to eq([49_154_040]) expect(get_timecodes(TEST_TIME, 15, 0)).to eq([49_154_039, 49_154_040]) expect(get_timecodes(TEST_TIME, 40, 0)).to eq([49_154_038, 49_154_039, 49_154_040]) expect(get_timecodes(TEST_TIME, 90, 0)).to eq([49_154_037, 49_154_038, 49_154_039, 49_154_040]) end it 'should get timecodes ahead' do expect(get_timecodes(TEST_TIME, 0, 15)).to eq([49_154_040]) expect(get_timecodes(TEST_TIME + 15, 0, 15)).to eq([49_154_040, 49_154_041]) expect(get_timecodes(TEST_TIME, 0, 30)).to eq([49_154_040, 49_154_041]) expect(get_timecodes(TEST_TIME, 0, 70)).to eq([49_154_040, 49_154_041, 49_154_042]) expect(get_timecodes(TEST_TIME, 0, 90)).to eq([49_154_040, 49_154_041, 49_154_042, 49_154_043]) end it 'should get timecodes behind and ahead' do expect(get_timecodes(TEST_TIME, 30, 30)).to eq([49_154_039, 49_154_040, 49_154_041]) expect(get_timecodes(TEST_TIME, 60, 60)).to eq([49_154_038, 49_154_039, 49_154_040, 49_154_041, 49_154_042]) end end describe '#verify with drift' do let(:verification) { totp.verify token, drift_ahead: drift_ahead, drift_behind: drift_behind, at: now } let(:drift_ahead) { 0 } let(:drift_behind) { 0 } context 'with an old OTP' do let(:token) { totp.at TEST_TIME - 30 } # Previous token at 2016-09-23 08:59:30 UTC let(:drift_behind) { 15 } # Tested at 2016-09-23 09:00:00 UTC, and with drift back to 2016-09-23 08:59:45 UTC # This would therefore include 2 intervals it 'inside of drift range' do expect(verification).to be_truthy end # Tested at 2016-09-23 09:00:20 UTC, and with drift back to 2016-09-23 09:00:05 UTC # This only includes 1 interval, therefore only the current token is valid context 'outside of drift range' do let(:now) { TEST_TIME + 20 } it 'is nil' do expect(verification).to be_nil end end end context 'with a future OTP' do let(:token) { totp.at TEST_TIME + 30 } # The next valid token - 2016-09-23 09:00:30 UTC let(:drift_ahead) { 15 } # Tested at 2016-09-23 09:00:00 UTC, and ahead to 2016-09-23 09:00:15 UTC # This only includes 1 interval, therefore only the current token is valid it 'outside of drift range' do expect(verification).to be_falsey end # Tested at 2016-09-23 09:00:20 UTC, and with drift ahead to 2016-09-23 09:00:35 UTC # This would therefore include 2 intervals context 'inside of drift range' do let(:now) { TEST_TIME + 20 } it 'is true' do expect(verification).to be_truthy end end end end describe '#verify with drift and prevent token reuse' do let(:verification) { totp.verify token, drift_ahead: drift_ahead, drift_behind: drift_behind, after: after, at: now } let(:drift_ahead) { 0 } let(:drift_behind) { 0 } let(:after) { nil } context 'with the `after` timestamp set' do context 'older token' do let(:token) { totp.at TEST_TIME - 30 } let(:drift_behind) { 15 } it 'is true' do expect(verification).to be_truthy expect(verification).to eq((TEST_TIME - 30).to_i) end context 'after it has been used' do let(:after) do totp.verify token, after: nil, at: now, drift_behind: drift_behind end it 'is false' do expect(verification).to be_falsey end end end context 'newer token' do let(:token) { totp.at TEST_TIME + 30 } let(:drift_ahead) { 15 } let(:now) { TEST_TIME + 15 } it 'is true' do expect(verification).to be_truthy expect(verification).to eq((TEST_TIME + 30).to_i) end context 'after it has been used' do let(:after) do totp.verify token, after: nil, at: now, drift_ahead: drift_ahead end it 'is false' do expect(verification).to be_falsey end end end end end describe '#provisioning_uri' do it 'accepts the account name' do expect(totp.provisioning_uri('mark@percival')) .to eq 'otpauth://totp/mark%40percival?secret=JBSWY3DPEHPK3PXP' end end describe '#now' do before do Timecop.freeze now end context 'Google Authenticator' do let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' } let(:now) { Time.at 1_297_553_958 } it 'matches the known output' do expect(totp.now).to eq '102705' end end context 'Dropbox 26 char secret output' do let(:totp) { ROTP::TOTP.new 'tjtpqea6a42l56g5eym73go2oa' } let(:now) { Time.at 1_378_762_454 } it 'matches the known output' do expect(totp.now).to eq '747864' end end end end rotp-6.2.0/spec/lib/rotp/arguments_spec.rb0000644000175100017510000000424113753334422017570 0ustar pravipravirequire 'spec_helper' require 'rotp/arguments' RSpec.describe ROTP::Arguments do let(:arguments) { described_class.new filename, argv } let(:argv) { '' } let(:filename) { 'rotp' } let(:options) { arguments.options } context 'without options' do describe '#help' do it 'shows the help text' do expect(arguments.to_s).to include 'Usage: ' end end describe '#options' do it 'has the default options' do expect(options.mode).to eq :time expect(options.secret).to be_nil expect(options.counter).to eq 0 end end end context 'unknown arguments' do let(:argv) { %w[--does-not-exist -xyz] } describe '#options' do it 'is in help mode' do expect(options.mode).to eq :help end it 'knows about the problem' do expect(options.warnings).to include 'invalid option: --does-not-exist' end end end context 'no arguments' do let(:argv) { [] } describe '#options' do it 'is in help mode' do expect(options.mode).to eq :help end end end context 'asking for help' do let(:argv) { %w[--help] } describe '#options' do it 'is in help mode' do expect(options.mode).to eq :help end end end context 'generating a counter based secret' do let(:argv) { %w[--hmac --secret s3same] } describe '#options' do it 'is in hmac mode' do expect(options.mode).to eq :hmac end it 'knows the secret' do expect(options.secret).to eq 's3same' end end end context 'generating a counter based secret' do let(:argv) { %w[--time --secret s3same] } describe '#options' do it 'is in hmac mode' do expect(options.mode).to eq :time end it 'knows the secret' do expect(options.secret).to eq 's3same' end end end context 'generating a time based secret' do let(:argv) { %w[--secret s3same] } describe '#options' do it 'is in time mode' do expect(options.mode).to eq :time end it 'knows the secret' do expect(options.secret).to eq 's3same' end end end end rotp-6.2.0/Guardfile0000644000175100017510000000055113753334422013365 0ustar pravipraviguard :rspec, cmd: 'bundle exec rspec --format progress' do require 'guard/rspec/dsl' dsl = Guard::RSpec::Dsl.new(self) # RSpec files rspec = dsl.rspec watch(rspec.spec_helper) { rspec.spec_dir } watch(rspec.spec_support) { rspec.spec_dir } watch(rspec.spec_files) # Ruby files ruby = dsl.ruby dsl.watch_spec_files_for(ruby.lib_files) end rotp-6.2.0/.travis.yml0000644000175100017510000000017613753334422013654 0ustar pravipravilanguage: ruby before_install: gem install bundler -v '<2' rvm: - 2.7 - 2.6 - 2.5 - 2.3 script: - bundle exec rspec rotp-6.2.0/doc/0000755000175100017510000000000013753334422012304 5ustar pravipravirotp-6.2.0/doc/index.html0000644000175100017510000001264113753334422014305 0ustar pravipravi Documentation by YARD 0.6.4

ROTP - The Ruby One Time Password Library

Installation

gem install rotp

Use

Time based OTP’s

totp = ROTP::TOTP.new("base32secretkey")
totp.now # => 492039
totp.at(Time.now.to_i + 30) # => 102922

Counter based OTP’s

hotp = ROTP::HOTP.new("base32secretkey")
hotp.at(0) # => 492039
hotp.at(1) # => 234092
hotp.at(2) # => 209834

Generating a Base32 Secret key

ROTP.random_base32 # returns a 16 character base32 secret. Compatible with Google Authenticator

Google Authenticator Interop

The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate provisioning URI’s for use with the QR Code scanner built into the app.

hotp.provisioning_uri # => 'otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP'

This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.

rotp-6.2.0/doc/ROTP/0000755000175100017510000000000013753334422013070 5ustar pravipravirotp-6.2.0/doc/ROTP/HOTP.html0000644000175100017510000001662313753334422014540 0ustar pravipravi Class: ROTP::HOTP

Class: ROTP::HOTP

Inherits:
OTP
  • Object
show all
Defined in:
lib/rotp/hotp.rb

Instance Method Summary (collapse)

Methods inherited from OTP

#byte_secret, #generate_otp, #initialize, #int_to_bytestring

Constructor Details

This class inherits a constructor from ROTP::OTP

Instance Method Details

- (Object) at(count)



3
4
5
# File 'lib/rotp/hotp.rb', line 3

def at(count)
  generate_otp(count)
end

- (String) provisioning_uri(name, start_count = 0)

Returns the provisioning URI for the OTP This can then be encoded in a QR Code and used to provision the Google Authenticator app

Parameters:

  • (String) name

    of the account

  • (Integer) initial

    counter value, defaults to 0

Returns:

  • (String)

    provisioning uri



13
14
15
# File 'lib/rotp/hotp.rb', line 13

def provisioning_uri(name, start_count=0)
  "otpauth://hotp/#{URI.encode(name)}?secret=#{secret}&counter=#{start_count}"
end
rotp-6.2.0/doc/ROTP/OTP.html0000644000175100017510000004207213753334422014425 0ustar pravipravi Class: ROTP::OTP

Class: ROTP::OTP

Inherits:
Object
  • Object
show all
Defined in:
lib/rotp/otp.rb

Direct Known Subclasses

HOTP, TOTP

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (OTP) initialize(s, options = {})

Params: secret in base32



6
7
8
9
10
# File 'lib/rotp/otp.rb', line 6

def initialize(s, options = {})
  @digits = options[:digits] || 6
  @digest = options[:digest] || "sha1"
  @secret = s
end

Instance Attribute Details

- (Object) digest (readonly)

Returns the value of attribute digest



3
4
5
# File 'lib/rotp/otp.rb', line 3

def digest
  @digest
end

- (Object) digits (readonly)

Returns the value of attribute digits



3
4
5
# File 'lib/rotp/otp.rb', line 3

def digits
  @digits
end

- (Object) secret (readonly)

Returns the value of attribute secret



3
4
5
# File 'lib/rotp/otp.rb', line 3

def secret
  @secret
end

Instance Method Details

- (Object) byte_secret



27
28
29
# File 'lib/rotp/otp.rb', line 27

def byte_secret
  Base32.decode(@secret)
end

- (Object) generate_otp(count)



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/rotp/otp.rb', line 12

def generate_otp(count)
  hmac = OpenSSL::HMAC.digest(
    OpenSSL::Digest::Digest.new(digest),
    byte_secret,
    int_to_bytestring(count)
  )

  offset = hmac[19] & 0xf
  code = (hmac[offset] & 0x7f) << 24 |
    (hmac[offset + 1] & 0xff) << 16 |
    (hmac[offset + 2] & 0xff) << 8 |
    (hmac[offset + 3] & 0xff)
  code % 10 ** digits
end

- (Object) int_to_bytestring(int, padding = 8)



31
32
33
34
35
36
37
38
# File 'lib/rotp/otp.rb', line 31

def int_to_bytestring(int, padding = 8)
  result = []
  until int == 0
    result << (int & 0xFF).chr
    int >>=  8
  end
  result.reverse.join.rjust(8, 0.chr)
end
rotp-6.2.0/doc/ROTP/TOTP.html0000644000175100017510000002751713753334422014560 0ustar pravipravi Class: ROTP::TOTP

Class: ROTP::TOTP

Inherits:
OTP
  • Object
show all
Defined in:
lib/rotp/totp.rb

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods inherited from OTP

#byte_secret, #generate_otp, #int_to_bytestring

Constructor Details

- (TOTP) initialize(s, options = {})

A new instance of TOTP



6
7
8
9
# File 'lib/rotp/totp.rb', line 6

def initialize(s, options = {})
  @interval = options[:interval] || 30
  super
end

Instance Attribute Details

- (Object) interval (readonly)

Returns the value of attribute interval



4
5
6
# File 'lib/rotp/totp.rb', line 4

def interval
  @interval
end

Instance Method Details

- (Object) at(time)



11
12
13
14
15
16
# File 'lib/rotp/totp.rb', line 11

def at(time)
  unless time.class == Time
    time = Time.at(time.to_i)
  end
  generate_otp(timecode(time))
end

- (Object) now



18
19
20
# File 'lib/rotp/totp.rb', line 18

def now
  generate_otp(timecode(Time.now))
end

- (String) provisioning_uri(name)

Returns the provisioning URI for the OTP This can then be encoded in a QR Code and used to provision the Google Authenticator app

Parameters:

  • (String) name

    of the account

Returns:

  • (String)

    provisioning uri



27
28
29
# File 'lib/rotp/totp.rb', line 27

def provisioning_uri(name)
  "otpauth://totp/#{URI.encode(name)}?secret=#{secret}"
end
rotp-6.2.0/doc/frames.html0000644000175100017510000000067413753334422014456 0ustar pravipravi Documentation by YARD 0.6.4 rotp-6.2.0/doc/top-level-namespace.html0000644000175100017510000000414713753334422017041 0ustar pravipravi Top Level Namespace

Top Level Namespace

Defined Under Namespace

Modules: ROTP, Rotp

rotp-6.2.0/doc/file.README.html0000644000175100017510000001264113753334422015051 0ustar pravipravi Documentation by YARD 0.6.4

ROTP - The Ruby One Time Password Library

Installation

gem install rotp

Use

Time based OTP’s

totp = ROTP::TOTP.new("base32secretkey")
totp.now # => 492039
totp.at(Time.now.to_i + 30) # => 102922

Counter based OTP’s

hotp = ROTP::HOTP.new("base32secretkey")
hotp.at(0) # => 492039
hotp.at(1) # => 234092
hotp.at(2) # => 209834

Generating a Base32 Secret key

ROTP.random_base32 # returns a 16 character base32 secret. Compatible with Google Authenticator

Google Authenticator Interop

The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate provisioning URI’s for use with the QR Code scanner built into the app.

hotp.provisioning_uri # => 'otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP'

This can then be rendered as a QR Code which can then be scanned and added to the users list of OTP credentials.

rotp-6.2.0/doc/Rotp.html0000644000175100017510000001203513753334422014117 0ustar pravipravi Module: ROTP

Module: ROTP

Defined in:
lib/rotp.rb,
lib/rotp/otp.rb,
lib/rotp/totp.rb,
lib/rotp/hotp.rb

Defined Under Namespace

Classes: HOTP, OTP, TOTP

Constant Summary

BASE32 =
'abcdefghijklmnopqrstuvwxyz234567'

Class Method Summary (collapse)

Class Method Details

+ (Object) random_base32(length = 16)



10
11
12
13
14
15
# File 'lib/rotp.rb', line 10

def self.random_base32(length=16)
  b32 = ''
  OpenSSL::Random.random_bytes(length).each_byte do |b|
    BASE32[b % 32, 1]
  end
end
rotp-6.2.0/doc/class_list.html0000644000175100017510000000421613753334422015335 0ustar pravipravi

Class List

rotp-6.2.0/doc/css/0000755000175100017510000000000013753334422013074 5ustar pravipravirotp-6.2.0/doc/css/common.css0000644000175100017510000000005213753334422015073 0ustar pravipravi/* Override this file with custom rules */rotp-6.2.0/doc/css/full_list.css0000644000175100017510000001236013753334422015605 0ustar pravipravibody { margin: 0; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; font-size: 13px; height: 101%; overflow-x: hidden; } h1 { padding: 12px 10px; padding-bottom: 0; margin: 0; font-size: 1.4em; } .clear { clear: both; } #search { position: absolute; right: 5px; top: 9px; padding-left: 24px; } #content.insearch #search, #content.insearch #noresults { background: url(data:image/gif;base64,R0lGODlhEAAQAPYAAP///wAAAPr6+pKSkoiIiO7u7sjIyNjY2J6engAAAI6OjsbGxjIyMlJSUuzs7KamppSUlPLy8oKCghwcHLKysqSkpJqamvT09Pj4+KioqM7OzkRERAwMDGBgYN7e3ujo6Ly8vCoqKjY2NkZGRtTU1MTExDw8PE5OTj4+PkhISNDQ0MrKylpaWrS0tOrq6nBwcKysrLi4uLq6ul5eXlxcXGJiYoaGhuDg4H5+fvz8/KKiohgYGCwsLFZWVgQEBFBQUMzMzDg4OFhYWBoaGvDw8NbW1pycnOLi4ubm5kBAQKqqqiQkJCAgIK6urnJyckpKSjQ0NGpqatLS0sDAwCYmJnx8fEJCQlRUVAoKCggICLCwsOTk5ExMTPb29ra2tmZmZmhoaNzc3KCgoBISEiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCAAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkIAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQgAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkIAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==) no-repeat center left; } #full_list { padding: 0; list-style: none; margin-left: 0; } #full_list ul { padding: 0; } #full_list li { padding: 5px; padding-left: 12px; margin: 0; font-size: 1.1em; list-style: none; } #noresults { padding: 7px 12px; } #content.insearch #noresults { margin-left: 7px; } ul.collapsed ul, ul.collapsed li { display: none; } li a.toggle { cursor: default; position: relative; left: -5px; top: 4px; text-indent: -999px; width: 10px; height: 9px; margin-left: -10px; display: block; float: left; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; } li.collapsed a.toggle { opacity: 0.5; cursor: default; background-position: top left; } li { color: #888; cursor: pointer; } li.deprecated { text-decoration: line-through; font-style: italic; } li.r1 { background: #f0f0f0; } li.r2 { background: #fafafa; } li:hover { background: #ddd; } li small:before { content: "("; } li small:after { content: ")"; } li small.search_info { display: none; } a:link, a:visited { text-decoration: none; color: #05a; } li.clicked { background: #05a; color: #ccc; } li.clicked a:link, li.clicked a:visited { color: #eee; } li.clicked a.toggle { opacity: 0.5; background-position: bottom right; } li.collapsed.clicked a.toggle { background-position: top right; } #search input { border: 1px solid #bbb; -moz-border-radius: 3px; -webkit-border-radius: 3px; } #nav { margin-left: 10px; font-size: 0.9em; display: none; color: #aaa; } #nav a:link, #nav a:visited { color: #358; } #nav a:hover { background: transparent; color: #5af; } .frames #content h1 { margin-top: 0; } .frames li { white-space: nowrap; cursor: normal; } .frames li small { display: block; font-size: 0.8em; } .frames li small:before { content: ""; } .frames li small:after { content: ""; } .frames li small.search_info { display: none; } .frames #search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #888; padding-left: 0; padding-right: 24px; } .frames #content.insearch #search { background-position: center right; } .frames #search input { width: 110px; } .frames #nav { display: block; } #full_list.insearch li { display: none; } #full_list.insearch li.found { display: list-item; padding-left: 10px; } #full_list.insearch li a.toggle { display: none; } #full_list.insearch li small.search_info { display: block; } rotp-6.2.0/doc/css/style.css0000644000175100017510000003747513753334422014766 0ustar pravipravibody { padding: 0 20px; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; font-size: 13px; } body.frames { padding: 0 5px; } h1 { font-size: 25px; margin: 1em 0 0.5em; padding-top: 4px; border-top: 1px dotted #d5d5d5; } h1.noborder { border-top: 0px; margin-top: 0; padding-top: 4px; } h1.title { margin-bottom: 10px; } h1.alphaindex { margin-top: 0; font-size: 22px; } h2 { padding: 0; padding-bottom: 3px; border-bottom: 1px #aaa solid; font-size: 1.4em; margin: 1.8em 0 0.5em; } h2 small { font-weight: normal; font-size: 0.7em; display: block; float: right; } .clear { clear: both; } .inline { display: inline; } .inline p:first-child { display: inline; } .docstring h1, .docstring h2, .docstring h3, .docstring h4 { padding: 0; border: 0; border-bottom: 1px dotted #bbb; } .docstring h1 { font-size: 1.2em; } .docstring h2 { font-size: 1.1em; } .docstring h3, .docstring h4 { font-size: 1em; border-bottom: 0; padding-top: 10px; } .docstring .object_link { font-family: monospace; } .note { color: #222; -moz-border-radius: 3px; -webkit-border-radius: 3px; background: #e3e4e3; border: 1px solid #d5d5d5; padding: 7px 10px; display: block; } .note.todo { background: #ffffc5; border-color: #ececaa; } .note.returns_void { background: #efefef; } .note.deprecated { background: #ffe5e5; border-color: #e9dada; } .note.private { background: #ffffc5; border-color: #ececaa; } .note.title { text-transform: lowercase; padding: 1px 5px; font-size: 0.9em; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; display: inline; } .summary_signature + .note.title { margin-left: 7px; } h1 .note.title { font-size: 0.5em; font-weight: normal; padding: 3px 5px; position: relative; top: -3px; text-transform: capitalize; } .note.title.constructor { color: #fff; background: #6a98d6; border-color: #6689d6; } .note.title.writeonly { color: #fff; background: #45a638; border-color: #2da31d; } .note.title.readonly { color: #fff; background: #6a98d6; border-color: #6689d6; } .note.title.private { background: #d5d5d5; border-color: #c5c5c5; } .discussion .note { margin-top: 6px; } .discussion .note:first-child { margin-top: 0; } h3.inherited { font-style: italic; font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; font-weight: normal; padding: 0; margin: 0; margin-top: 12px; margin-bottom: 3px; font-size: 13px; } p.inherited { padding: 0; margin: 0; margin-left: 25px; } dl.box { width: 520px; font-size: 1em; } dl.box dt { float: left; display: block; width: 100px; margin: 0; text-align: right; font-weight: bold; border: 1px solid #aaa; border-width: 1px 0px 0px 1px; padding: 6px 0; padding-right: 10px; } dl.box dd { float: left; display: block; width: 380px; margin: 0; padding: 6px 0; padding-right: 20px; border: 1px solid #aaa; border-width: 1px 1px 0 0; } dl.box .last { border-bottom: 1px solid #aaa; } dl.box .r1 { background: #eee; } ul.toplevel { list-style: none; padding-left: 0; font-size: 1.1em; } #files { padding-left: 15px; font-size: 1.1em; } #files { padding: 0; } #files li { list-style: none; display: inline; padding: 7px 12px; line-height: 35px; } dl.constants { margin-left: 40px; } dl.constants dt { font-weight: bold; font-size: 1.1em; margin-bottom: 5px; } dl.constants dd { width: 75%; white-space: pre; font-family: monospace; margin-bottom: 18px; } .summary_desc { margin-left: 32px; display: block; font-family: sans-serif; } .summary_desc tt { font-size: 0.9em; } dl.constants .note { padding: 2px 6px; padding-right: 12px; margin-top: 6px; } dl.constants .docstring { margin-left: 32px; font-size: 0.9em; font-weight: normal; } dl.constants .tags { padding-left: 32px; font-size: 0.9em; line-height: 0.8em; } dl.constants .discussion *:first-child { margin-top: 0; } dl.constants .discussion *:last-child { margin-bottom: 0; } .method_details { border-top: 1px dotted #aaa; margin-top: 15px; padding-top: 0; } .method_details.first { border: 0; } p.signature { font-size: 1.1em; font-weight: normal; font-family: Monaco, Consolas, Courier, monospace; padding: 6px 10px; margin-top: 18px; background: #e5e8ff; border: 1px solid #d8d8e5; -moz-border-radius: 3px; -webkit-border-radius: 3px; } p.signature tt { font-family: Monaco, Consolas, Courier, monospace; } p.signature .overload { display: block; } p.signature .extras { font-weight: normal; font-family: sans-serif; color: #444; font-size: 1em; } p.signature .aliases { display: block; font-weight: normal; font-size: 0.9em; font-family: sans-serif; margin-top: 0px; color: #555; } p.signature .aliases .names { font-family: Monaco, Consolas, Courier, monospace; font-weight: bold; color: #000; font-size: 1.2em; } .tags h3 { font-size: 1em; margin-bottom: 0; } .tags ul { margin-top: 5px; padding-left: 30px; list-style: square; } .tags ul li { margin-bottom: 3px; } .tags ul .name { font-family: monospace; font-weight: bold; } .tags ul .note { padding: 3px 6px; } .tags { margin-bottom: 12px; } .tags .examples h3 { margin-bottom: 10px; } .tags .examples h4 { padding: 0; margin: 0; margin-left: 15px; font-weight: bold; font-size: 0.9em; } .tags .overload .overload_item { list-style: none; margin-bottom: 25px; } .tags .overload .overload_item .signature { padding: 2px 8px; background: #e5e8ff; border: 1px solid #d8d8e5; -moz-border-radius: 3px; -webkit-border-radius: 3px; } .tags .overload .signature { margin-left: -15px; font-family: monospace; display: block; font-size: 1.1em; } .tags .overload .docstring { margin-top: 15px; } .defines { display: none; } #method_missing_details .notice.this { position: relative; top: -8px; color: #888; padding: 0; margin: 0; } .showSource { font-size: 0.9em; } .showSource a:link, .showSource a:visited { text-decoration: none; color: #666; } #content a:link, #content a:visited { text-decoration: none; color: #05a; } #content a:hover { background: #ffffa5; } .docstring { margin-right: 6em; } ul.summary { list-style: none; font-family: monospace; font-size: 1em; line-height: 1.5em; } ul.summary a:link, ul.summary a:visited { text-decoration: none; font-size: 1.1em; } ul.summary li { margin-bottom: 5px; } .summary .summary_signature { padding: 1px 10px; background: #eaeaff; border: 1px solid #dfdfe5; -moz-border-radius: 3px; -webkit-border-radius: 3px; } .summary_signature:hover { background: #eeeeff; cursor: pointer; } ul.summary.compact li { display: inline-block; margin: 0px 5px 0px 0px; line-height: 2.6em;} ul.summary.compact .summary_signature { padding: 5px 7px; padding-right: 4px; } #content .summary_signature:hover a:link, #content .summary_signature:hover a:visited { background: transparent; color: #48f; } p.inherited a { font-family: monospace; font-size: 0.9em; } p.inherited { word-spacing: 5px; font-size: 1.2em; } p.children { font-size: 1.2em; } p.children a { font-size: 0.9em; } p.children strong { font-size: 0.8em; } p.children strong.modules { padding-left: 5px; } ul.fullTree { display: none; padding-left: 0; list-style: none; margin-left: 0; margin-bottom: 10px; } ul.fullTree ul { margin-left: 0; padding-left: 0; list-style: none; } ul.fullTree li { text-align: center; padding-top: 18px; padding-bottom: 12px; background: url(data:image/gif;base64,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) no-repeat top center; } ul.fullTree li:first-child { padding-top: 0; background: transparent; } ul.fullTree li:last-child { padding-bottom: 0; } .showAll ul.fullTree { display: block; } .showAll .inheritName { display: none; } #search { position: absolute; right: 14px; top: 0px; } #search a:link, #search a:visited { display: block; float: left; margin-right: 4px; padding: 8px 10px; text-decoration: none; color: #05a; border: 1px solid #d8d8e5; -moz-border-radius-bottomleft: 3px; -moz-border-radius-bottomright: 3px; -webkit-border-bottom-left-radius: 3px; -webkit-border-bottom-right-radius: 3px; background: #eaf0ff; -webkit-box-shadow: -1px 1px 3px #ddd; } #search a:hover { background: #f5faff; color: #06b; } #search a.active { background: #568; padding-bottom: 20px; color: #fff; border: 1px solid #457; -moz-border-radius-topleft: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-left-radius: 5px; -webkit-border-top-right-radius: 5px; } #search a.inactive { color: #999; } .frames #search { display: none; } .inheritanceTree, .toggleDefines { float: right; } #menu { font-size: 1.3em; color: #bbb; top: -5px; position: relative; } #menu .title, #menu a { font-size: 0.7em; } #menu .title a { font-size: 1em; } #menu .title { color: #555; } #menu a:link, #menu a:visited { color: #333; text-decoration: none; border-bottom: 1px dotted #bbd; } #menu a:hover { color: #05a; } #menu .noframes { display: none; } .frames #menu .noframes { display: inline; float: right; } #footer { margin-top: 15px; border-top: 1px solid #ccc; text-align: center; padding: 7px 0; color: #999; } #footer a:link, #footer a:visited { color: #444; text-decoration: none; border-bottom: 1px dotted #bbd; } #footer a:hover { color: #05a; } #listing ul.alpha { font-size: 1.1em; } #listing ul.alpha { margin: 0; padding: 0; padding-bottom: 10px; list-style: none; } #listing ul.alpha li.letter { font-size: 1.4em; padding-bottom: 10px; } #listing ul.alpha ul { margin: 0; padding-left: 15px; } #listing ul small { color: #666; font-size: 0.7em; } li.r1 { background: #f0f0f0; } li.r2 { background: #fafafa; } #search_frame { z-index: 9999; background: #fff; display: none; position: absolute; top: 36px; right: 18px; width: 500px; height: 80%; overflow-y: scroll; border: 1px solid #999; border-collapse: collapse; -webkit-box-shadow: -7px 5px 25px #aaa; -moz-box-shadow: -7px 5px 25px #aaa; -moz-border-radius: 2px; -webkit-border-radius: 2px; } #content ul.summary li.deprecated a:link, #content ul.summary li.deprecated a:visited { text-decoration: line-through; font-style: italic; } #toc { padding: 20px; padding-right: 30px; border: 1px solid #ddd; float: right; background: #fff; margin-left: 20px; margin-bottom: 20px; max-width: 300px; -webkit-box-shadow: -2px 2px 6px #bbb; -moz-box-shadow: -2px 2px 6px #bbb; z-index: 5000; position: relative; } #toc.nofloat { float: none; max-width: none; border: none; padding: 0; margin: 20px 0; -webkit-box-shadow: none; -moz-box-shadow: none; } #toc.nofloat.hidden { padding: 0; background: 0; margin-bottom: 5px; } #toc .title { margin: 0; } #toc ol { padding-left: 1.8em; } #toc li { font-size: 1.1em; line-height: 1.7em; } #toc > ol > li { font-size: 1.1em; font-weight: bold; } #toc ol > ol { font-size: 0.9em; } #toc ol ol > ol { padding-left: 2.3em; } #toc ol + li { margin-top: 0.3em; } #toc.hidden { padding: 10px; background: #f6f6f6; -webkit-box-shadow: none; -moz-box-shadow: none; } #filecontents h1 + #toc.nofloat { margin-top: 0; } /* syntax highlighting */ .source_code { display: none; padding: 3px 8px; border-left: 8px solid #ddd; margin-top: 5px; } #filecontents pre.code, .docstring pre.code, .source_code pre { font-family: monospace; } #filecontents pre.code, .docstring pre.code { display: block; } .source_code .lines { padding-right: 12px; color: #555; text-align: right; } #filecontents pre.code, .docstring pre.code, .tags .example { padding: 5px 12px; margin-top: 4px; border: 1px solid #eef; background: #f5f5ff; } pre.code { color: #000; } pre.code .info.file { color: #555; } pre.code .val { color: #036A07; } pre.code .tstring_content, pre.code .heredoc_beg, pre.code .heredoc_end, pre.code .qwords_beg, pre.code .qwords_end, pre.code .tstring, pre.code .dstring { color: #036A07; } pre.code .fid, pre.code .id.new, pre.code .id.to_s, pre.code .id.to_sym, pre.code .id.to_f, pre.code .dot + pre.code .id, pre.code .id.to_i pre.code .id.each { color: #0085FF; } pre.code .comment { color: #0066FF; } pre.code .const, pre.code .constant { color: #585CF6; } pre.code .symbol { color: #C5060B; } pre.code .kw, pre.code .label, pre.code .id.require, pre.code .id.extend, pre.code .id.include { color: #0000FF; } pre.code .ivar { color: #318495; } pre.code .gvar, pre.code .id.backref, pre.code .id.nth_ref { color: #6D79DE; } pre.code .regexp, .dregexp { color: #036A07; } pre.code a { border-bottom: 1px dotted #bbf; } rotp-6.2.0/doc/method_list.html0000644000175100017510000001061613753334422015511 0ustar pravipravi

Method List

rotp-6.2.0/doc/js/0000755000175100017510000000000013753334422012720 5ustar pravipravirotp-6.2.0/doc/js/app.js0000644000175100017510000001435013753334422014041 0ustar pravipravifunction createSourceLinks() { $('.method_details_list .source_code'). before("[View source]"); $('.toggleSource').toggle(function() { $(this).parent().next().slideDown(100); $(this).text("Hide source"); }, function() { $(this).parent().next().slideUp(100); $(this).text("View source"); }); } function createDefineLinks() { var tHeight = 0; $('.defines').after(" more..."); $('.toggleDefines').toggle(function() { tHeight = $(this).parent().prev().height(); $(this).prev().show(); $(this).parent().prev().height($(this).parent().height()); $(this).text("(less)"); }, function() { $(this).prev().hide(); $(this).parent().prev().height(tHeight); $(this).text("more...") }); } function createFullTreeLinks() { var tHeight = 0; $('.inheritanceTree').toggle(function() { tHeight = $(this).parent().prev().height(); $(this).parent().toggleClass('showAll'); $(this).text("(hide)"); $(this).parent().prev().height($(this).parent().height()); }, function() { $(this).parent().toggleClass('showAll'); $(this).parent().prev().height(tHeight); $(this).text("show all") }); } function fixBoxInfoHeights() { $('dl.box dd.r1, dl.box dd.r2').each(function() { $(this).prev().height($(this).height()); }); } function searchFrameLinks() { $('#method_list_link').click(function() { toggleSearchFrame(this, relpath + 'method_list.html'); }); $('#class_list_link').click(function() { toggleSearchFrame(this, relpath + 'class_list.html'); }); $('#file_list_link').click(function() { toggleSearchFrame(this, relpath + 'file_list.html'); }); } function toggleSearchFrame(id, link) { var frame = $('#search_frame'); $('#search a').removeClass('active').addClass('inactive'); if (frame.attr('src') == link && frame.css('display') != "none") { frame.slideUp(100); $('#search a').removeClass('active inactive'); } else { $(id).addClass('active').removeClass('inactive'); frame.attr('src', link).slideDown(100); } } function linkSummaries() { $('.summary_signature').click(function() { document.location = $(this).find('a').attr('href'); }); } function framesInit() { if (window.top.frames.main) { document.body.className = 'frames'; $('#menu .noframes a').attr('href', document.location); $('html head title', window.parent.document).text($('html head title').text()); } } function keyboardShortcuts() { if (window.top.frames.main) return; $(document).keypress(function(evt) { if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) return; if (typeof evt.orignalTarget !== "undefined" && (evt.originalTarget.nodeName == "INPUT" || evt.originalTarget.nodeName == "TEXTAREA")) return; switch (evt.charCode) { case 67: case 99: $('#class_list_link').click(); break; // 'c' case 77: case 109: $('#method_list_link').click(); break; // 'm' case 70: case 102: $('#file_list_link').click(); break; // 'f' } }); } function summaryToggle() { $('.summary_toggle').click(function() { localStorage.summaryCollapsed = $(this).text(); $(this).text($(this).text() == "collapse" ? "expand" : "collapse"); var next = $(this).parent().parent().next(); if (next.hasClass('compact')) { next.toggle(); next.next().toggle(); } else if (next.hasClass('summary')) { var list = $('