Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Just a set of POROs for emulating a simple OAuth2 provider
require 'securerandom'
module Oauth
class StubProvider
AUTHORIZATION_TTL = 60.seconds
ACCESS_TTL = 1.hour
def initialize(authorization_ttl: AUTHORIZATION_TTL, access_ttl: ACCESS_TTL, **meta)
@authorization_ttl = authorization_ttl
@access_ttl = access_ttl
@tokens = []
end
private def tokens
@tokens.reject! { |t| t.expired? }
@tokens
end
class Token
attr_reader :expiry
def initialize(authorization_ttl:, access_ttl:, **meta)
@authorization_code = SecureRandom.uuid
@meta = meta
@expiry = Time.now + authorization_ttl
@access_ttl = access_ttl
end
def authorization_code
@authorization_code
end
def authorize!(authorization_code)
if authorization_code.present? && !expired? && @authorization_code == authorization_code
set_tokens
else
false
end
end
def info(access_token)
if access_token.present? && !expired? && @access_token == access_token
@meta
else
false
end
end
def refresh!(refresh_token)
if refresh_token.present? && !expired? && @refresh_token == refresh_token
set_tokens
else
false
end
end
private def set_tokens
@authorization_code = nil
@access_token = SecureRandom.uuid
@refresh_token = SecureRandom.uuid
@expiry = Time.now + @access_ttl
{
access_token: @access_token,
refresh_token: @refresh_token,
expiry: expiry
}
end
def expired?
Time.now > expiry
end
end
def generate_authorization(**meta)
token = Token.new(**meta, authorization_ttl: @authorization_ttl, access_ttl: @access_ttl)
tokens << token
{
authorization_code: token.authorization_code,
expiry: token.expiry,
}
end
def authorize(code)
tokens.each { |t| return (t.authorize!(code) || next) }
false
end
def refresh(refresh_token)
tokens.each { |t| return (t.refresh!(refresh_token) || next) }
false
end
def info(access_token)
tokens.each { |t| return (t.info(access_token) || next) }
false
end
end
end
require 'spec_helper'
RSpec.describe Oauth::StubProvider do
let(:provider) { described_class.new }
it 'generates an authorization token' do
expect(provider.generate_authorization).to match hash_including({
expiry: instance_of(Time),
authorization_code: /.+/,
})
end
it 'does not authorize expired authorization tokens' do
data = provider.generate_authorization
Timecop.travel(data[:expiry] + 1.second) do
expect(provider.authorize(data[:authorization_code])).to eq false
end
end
it 'does not authorize invalid authorization tokens' do
provider.generate_authorization
expect(provider.authorize(SecureRandom.uuid)).to eq false
end
it 'authorizes valid authorization token exactly once' do
data = provider.generate_authorization
expect(provider.authorize(data[:authorization_code])).to match hash_including({
expiry: instance_of(Time),
access_token: /.+/,
refresh_token: /.+/,
})
expect(provider.authorize(data[:authorization_code])).to eq false
end
it 'does not give access with an authorization token' do
auth = provider.generate_authorization[:authorization_code]
expect(provider.info(auth)).to eq false
end
it 'does not refresh with an authorization token' do
auth = provider.generate_authorization[:authorization_code]
expect(provider.refresh(auth)).to eq false
end
it 'does not refresh with an expired refresh token' do
auth = provider.generate_authorization[:authorization_code]
data = provider.authorize(auth)
Timecop.travel(data[:expiry] + 1.second) do
expect(provider.refresh(data[:refresh_token])).to eq false
end
end
it 'does not refresh with an access token' do
auth = provider.generate_authorization[:authorization_code]
data = provider.authorize(auth)
expect(provider.refresh(data[:access_token])).to eq false
end
it 'refreshes tokens with valid refresh token and invalidates previous tokens' do
auth = provider.generate_authorization[:authorization_code]
data = provider.authorize(auth)
refreshed_data = provider.refresh(data[:refresh_token])
expect(refreshed_data).to match hash_including({
expiry: instance_of(Time),
access_token: /.+/,
refresh_token: /.+/,
})
expect(refreshed_data[:expiry]).to be > data[:expiry]
expect(refreshed_data[:access_token]).to_not eq data[:access_token]
expect(refreshed_data[:access_token]).to_not eq data[:access_token]
expect(provider.info(data[:access_token])).to eq false
expect(provider.refresh(data[:refresh_token])).to eq false
expect(provider.info(refreshed_data[:access_token])).to_not eq false
end
it 'does not return token info when using an expired access token' do
auth = provider.generate_authorization[:authorization_code]
data = provider.authorize(auth)
Timecop.travel(data[:expiry] + 1.second) do
expect(provider.info(data[:access_token])).to eq false
end
end
it 'extends expiry when refreshing' do
auth = provider.generate_authorization[:authorization_code]
data = provider.authorize(auth)
Timecop.travel(data[:expiry] - 1.second) do
refreshed_data = provider.refresh(data[:refresh_token])
expect(refreshed_data[:expiry]).to be > data[:expiry]
end
end
it 'does not return token info when using an invalid access token' do
auth = provider.generate_authorization[:authorization_code]
token = provider.authorize(auth)[:access_token]
expect(provider.info(SecureRandom.uuid)).to eq false
end
it 'returns token info associated with initial grant' do
info = { data: Object.new }
auth = provider.generate_authorization(info)[:authorization_code]
token = provider.authorize(auth)[:access_token]
expect(provider.info(token)).to eql info
end
it 'allows configuring the expiry TTLs' do
authorization_ttl = 1.second
access_ttl = 10.seconds
provider = described_class.new(access_ttl: access_ttl, authorization_ttl: authorization_ttl)
auth = data = nil
Timecop.freeze do
auth = provider.generate_authorization
expect(auth[:expiry]).to eq(Time.now + authorization_ttl)
end
Timecop.freeze do
data = provider.authorize(auth[:authorization_code])
expect(data[:expiry]).to eq(Time.now + access_ttl)
end
Timecop.freeze do
data = provider.refresh(data[:refresh_token])
expect(data[:expiry]).to eq(Time.now + access_ttl)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.