Navigation Menu

Skip to content

Commit

Permalink
add rubocop and auto correct
Browse files Browse the repository at this point in the history
  • Loading branch information
zaru committed Apr 21, 2019
1 parent 2268694 commit c1d5dc5
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 188 deletions.
8 changes: 8 additions & 0 deletions .rubocop.yml
@@ -0,0 +1,8 @@
require: rubocop-performance

AllCops:
Exclude:
- 'bin/**/*'

Metrics/LineLength:
Enabled: false
4 changes: 2 additions & 2 deletions Rakefile
@@ -1,5 +1,5 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

Expand Down
6 changes: 2 additions & 4 deletions lib/webpush.rb
Expand Up @@ -28,7 +28,7 @@ class << self
# @param options [Hash<Symbol,String>] additional options for the notification
# @option options [#to_s] :ttl Time-to-live in seconds
# @option options [#to_s] :urgency Urgency can be very-low, low, normal, high
def payload_send(message: "", endpoint:, p256dh: "", auth: "", vapid: {}, **options)
def payload_send(message: '', endpoint:, p256dh: '', auth: '', vapid: {}, **options)
subscription = {
endpoint: endpoint,
keys: {
Expand Down Expand Up @@ -59,9 +59,7 @@ def encode64(bytes)
def decode64(str)
# For Ruby < 2.3, Base64.urlsafe_decode64 strict decodes and will raise errors if encoded value is not properly padded
# Implementation: http://ruby-doc.org/stdlib-2.3.0/libdoc/base64/rdoc/Base64.html#method-i-urlsafe_decode64
if !str.end_with?("=") && str.length % 4 != 0
str = str.ljust((str.length + 3) & ~3, "=")
end
str = str.ljust((str.length + 3) & ~3, '=') if !str.end_with?('=') && str.length % 4 != 0

Base64.urlsafe_decode64(str)
end
Expand Down
18 changes: 9 additions & 9 deletions lib/webpush/encryption.rb
Expand Up @@ -5,7 +5,7 @@ module Encryption
def encrypt(message, p256dh, auth)
assert_arguments(message, p256dh, auth)

group_name = "prime256v1"
group_name = 'prime256v1'
salt = Random.new.bytes(16)

server = OpenSSL::PKey::EC.new(group_name)
Expand Down Expand Up @@ -47,9 +47,9 @@ def create_context(client_public_key, server_public_key)
c = convert16bit(client_public_key)
s = convert16bit(server_public_key)
context = "\0"
context += [c.bytesize].pack("n*")
context += [c.bytesize].pack('n*')
context += c
context += [s.bytesize].pack("n*")
context += [s.bytesize].pack('n*')
context += s
context
end
Expand All @@ -69,22 +69,22 @@ def encrypt_payload(plaintext, content_encryption_key, nonce)
end

def create_info(type, context)
info = "Content-Encoding: "
info = 'Content-Encoding: '
info += type
info += "\0"
info += "P-256"
info += 'P-256'
info += context
info
end

def convert16bit(key)
[key.to_s(16)].pack("H*")
[key.to_s(16)].pack('H*')
end

def assert_arguments(message, p256dh, auth)
raise ArgumentError, "message cannot be blank" if blank?(message)
raise ArgumentError, "p256dh cannot be blank" if blank?(p256dh)
raise ArgumentError, "auth cannot be blank" if blank?(auth)
raise ArgumentError, 'message cannot be blank' if blank?(message)
raise ArgumentError, 'p256dh cannot be blank' if blank?(p256dh)
raise ArgumentError, 'auth cannot be blank' if blank?(auth)
end

def blank?(value)
Expand Down
3 changes: 1 addition & 2 deletions lib/webpush/errors.rb
Expand Up @@ -3,7 +3,7 @@ class Error < RuntimeError; end

class ConfigurationError < Error; end

class ResponseError < Error;
class ResponseError < Error
attr_reader :response, :host

def initialize(response, host)
Expand All @@ -24,5 +24,4 @@ class PayloadTooLarge < ResponseError; end
class TooManyRequests < ResponseError; end

class PushServiceError < ResponseError; end

end
46 changes: 23 additions & 23 deletions lib/webpush/request.rb
Expand Up @@ -3,11 +3,11 @@

module Webpush
# It is temporary URL until supported by the GCM server.
GCM_URL = 'https://android.googleapis.com/gcm/send'
TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'
GCM_URL = 'https://android.googleapis.com/gcm/send'.freeze
TEMP_GCM_URL = 'https://gcm-http.googleapis.com/gcm'.freeze

class Request
def initialize(message: "", subscription:, vapid:, **options)
def initialize(message: '', subscription:, vapid:, **options)
endpoint = subscription.fetch(:endpoint)
@endpoint = endpoint.gsub(GCM_URL, TEMP_GCM_URL)
@payload = build_payload(message, subscription)
Expand All @@ -31,15 +31,15 @@ def perform
elsif resp.is_a?(Net::HTTPNotFound) # 404
raise InvalidSubscription.new(resp, uri.host)
elsif resp.is_a?(Net::HTTPUnauthorized) || resp.is_a?(Net::HTTPForbidden) || # 401, 403
resp.is_a?(Net::HTTPBadRequest) && resp.message == "UnauthorizedRegistration" # 400, Google FCM
resp.is_a?(Net::HTTPBadRequest) && resp.message == 'UnauthorizedRegistration' # 400, Google FCM
raise Unauthorized.new(resp, uri.host)
elsif resp.is_a?(Net::HTTPRequestEntityTooLarge) # 413
raise PayloadTooLarge.new(resp, uri.host)
elsif resp.is_a?(Net::HTTPTooManyRequests) # 429, try again later!
raise TooManyRequests.new(resp, uri.host)
elsif resp.is_a?(Net::HTTPServerError) # 5xx
raise PushServiceError.new(resp, uri.host)
elsif !resp.is_a?(Net::HTTPSuccess) # unknown/unhandled response error
elsif !resp.is_a?(Net::HTTPSuccess) # unknown/unhandled response error
raise ResponseError.new(resp, uri.host)
end

Expand All @@ -48,22 +48,22 @@ def perform

def headers
headers = {}
headers["Content-Type"] = "application/octet-stream"
headers["Ttl"] = ttl
headers["Urgency"] = urgency

if @payload.has_key?(:server_public_key)
headers["Content-Encoding"] = "aesgcm"
headers["Encryption"] = "salt=#{salt_param}"
headers["Crypto-Key"] = "dh=#{dh_param}"
headers['Content-Type'] = 'application/octet-stream'
headers['Ttl'] = ttl
headers['Urgency'] = urgency

if @payload.key?(:server_public_key)
headers['Content-Encoding'] = 'aesgcm'
headers['Encryption'] = "salt=#{salt_param}"
headers['Crypto-Key'] = "dh=#{dh_param}"
end

if api_key?
headers["Authorization"] = "key=#{api_key}"
headers['Authorization'] = "key=#{api_key}"
elsif vapid?
vapid_headers = build_vapid_headers
headers["Authorization"] = vapid_headers["Authorization"]
headers["Crypto-Key"] = [ headers["Crypto-Key"], vapid_headers["Crypto-Key"] ].compact.join(";")
headers['Authorization'] = vapid_headers['Authorization']
headers['Crypto-Key'] = [headers['Crypto-Key'], vapid_headers['Crypto-Key']].compact.join(';')
end

headers
Expand All @@ -76,12 +76,12 @@ def build_vapid_headers

{
'Authorization' => 'WebPush ' + jwt,
'Crypto-Key' => 'p256ecdsa=' + p256ecdsa,
'Crypto-Key' => 'p256ecdsa=' + p256ecdsa
}
end

def body
@payload.fetch(:ciphertext, "")
@payload.fetch(:ciphertext, '')
end

private
Expand Down Expand Up @@ -110,7 +110,7 @@ def jwt_payload
{
aud: audience,
exp: Time.now.to_i + expiration,
sub: subject,
sub: subject
}
end

Expand All @@ -119,11 +119,11 @@ def jwt_header_fields
end

def audience
uri.scheme + "://" + uri.host
uri.scheme + '://' + uri.host
end

def expiration
@vapid_options.fetch(:expiration, 24*60*60)
@vapid_options.fetch(:expiration, 24 * 60 * 60)
end

def subject
Expand All @@ -144,7 +144,7 @@ def vapid_pem

def default_options
{
ttl: 60*60*24*7*4, # 4 weeks
ttl: 60 * 60 * 24 * 7 * 4, # 4 weeks
urgency: 'normal'
}
end
Expand All @@ -164,7 +164,7 @@ def api_key
end

def api_key?
!(api_key.nil? || api_key.empty?) && @endpoint =~ /\Ahttps:\/\/(android|gcm-http)\.googleapis\.com/
!(api_key.nil? || api_key.empty?) && @endpoint =~ %r{\Ahttps://(android|gcm-http)\.googleapis\.com}
end

def vapid?
Expand Down
4 changes: 2 additions & 2 deletions lib/webpush/vapid_key.rb
Expand Up @@ -78,12 +78,12 @@ def to_h
def to_pem
public_key = OpenSSL::PKey::EC.new curve
public_key.private_key = nil

curve.to_pem + public_key.to_pem
end

def inspect
"#<#{self.class}:#{object_id.to_s(16)} #{to_h.map { |k, v| ":#{k}=#{v}" }.join(" ")}>"
"#<#{self.class}:#{object_id.to_s(16)} #{to_h.map { |k, v| ":#{k}=#{v}" }.join(' ')}>"
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/webpush/version.rb
@@ -1,3 +1,3 @@
module Webpush
VERSION = "0.3.8"
VERSION = '0.3.8'.freeze
end
8 changes: 4 additions & 4 deletions spec/spec_helper.rb
@@ -1,4 +1,4 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'pry'
require 'webpush'
require 'webmock/rspec'
Expand All @@ -8,16 +8,16 @@

def vapid_options
{
subject: "mailto:sender@example.com",
subject: 'mailto:sender@example.com',
public_key: vapid_public_key,
private_key: vapid_private_key
}
end

def vapid_public_key
"BB37UCyc8LLX4PNQSe-04vSFvpUWGrENubUaslVFM_l5TxcGVMY0C3RXPeUJAQHKYlcOM2P4vTYmkoo0VZGZTM4="
'BB37UCyc8LLX4PNQSe-04vSFvpUWGrENubUaslVFM_l5TxcGVMY0C3RXPeUJAQHKYlcOM2P4vTYmkoo0VZGZTM4='
end

def vapid_private_key
"OPrw1Sum3gRoL4-DXfSCC266r-qfFSRZrnj8MgIhRHg="
'OPrw1Sum3gRoL4-DXfSCC266r-qfFSRZrnj8MgIhRHg='
end
54 changes: 26 additions & 28 deletions spec/webpush/encryption_spec.rb
Expand Up @@ -2,63 +2,63 @@
require 'ece'

describe Webpush::Encryption do
describe "#encrypt" do
describe '#encrypt' do
let(:p256dh) do
encode64(generate_ecdh_key)
end

let(:auth) { encode64(Random.new.bytes(16)) }

it "returns ECDH encrypted cipher text, salt, and server_public_key" do
payload = Webpush::Encryption.encrypt("Hello World", p256dh, auth)
it 'returns ECDH encrypted cipher text, salt, and server_public_key' do
payload = Webpush::Encryption.encrypt('Hello World', p256dh, auth)

encrypted = payload.fetch(:ciphertext)

decrypted_data = ECE.decrypt(encrypted,
key: payload.fetch(:shared_secret),
salt: payload.fetch(:salt),
server_public_key: payload.fetch(:server_public_key_bn),
user_public_key: decode64(p256dh),
auth: decode64(auth))
key: payload.fetch(:shared_secret),
salt: payload.fetch(:salt),
server_public_key: payload.fetch(:server_public_key_bn),
user_public_key: decode64(p256dh),
auth: decode64(auth))

expect(decrypted_data).to eq("Hello World")
expect(decrypted_data).to eq('Hello World')
end

it 'returns error when message is blank' do
expect{Webpush::Encryption.encrypt(nil, p256dh, auth)}.to raise_error(ArgumentError)
expect{Webpush::Encryption.encrypt("", p256dh, auth)}.to raise_error(ArgumentError)
expect { Webpush::Encryption.encrypt(nil, p256dh, auth) }.to raise_error(ArgumentError)
expect { Webpush::Encryption.encrypt('', p256dh, auth) }.to raise_error(ArgumentError)
end

it 'returns error when p256dh is blank' do
expect{Webpush::Encryption.encrypt("Hello world", nil, auth)}.to raise_error(ArgumentError)
expect{Webpush::Encryption.encrypt("Hello world", "", auth)}.to raise_error(ArgumentError)
expect { Webpush::Encryption.encrypt('Hello world', nil, auth) }.to raise_error(ArgumentError)
expect { Webpush::Encryption.encrypt('Hello world', '', auth) }.to raise_error(ArgumentError)
end

it 'returns error when auth is blank' do
expect{Webpush::Encryption.encrypt("Hello world", p256dh, "")}.to raise_error(ArgumentError)
expect{Webpush::Encryption.encrypt("Hello world", p256dh, nil)}.to raise_error(ArgumentError)
expect { Webpush::Encryption.encrypt('Hello world', p256dh, '') }.to raise_error(ArgumentError)
expect { Webpush::Encryption.encrypt('Hello world', p256dh, nil) }.to raise_error(ArgumentError)
end

# Bug fix for https://github.com/zaru/webpush/issues/22
it 'handles unpadded base64 encoded subscription keys' do
unpadded_p256dh = "BK74n-ZA6kfMDEuCFbQH1Y5T33p39PvnzNeuD5LqTs8cF-uaQFUHn_v5kwV6dYIIL4nFabxghQNF_vlnAXX7OiU"
unpadded_auth = "1C1PBkJQsVwD9tkuLR1x5A"
unpadded_p256dh = 'BK74n-ZA6kfMDEuCFbQH1Y5T33p39PvnzNeuD5LqTs8cF-uaQFUHn_v5kwV6dYIIL4nFabxghQNF_vlnAXX7OiU'
unpadded_auth = '1C1PBkJQsVwD9tkuLR1x5A'

payload = Webpush::Encryption.encrypt("Hello World", unpadded_p256dh, unpadded_auth)
payload = Webpush::Encryption.encrypt('Hello World', unpadded_p256dh, unpadded_auth)
encrypted = payload.fetch(:ciphertext)

decrypted_data = ECE.decrypt(encrypted,
key: payload.fetch(:shared_secret),
salt: payload.fetch(:salt),
server_public_key: payload.fetch(:server_public_key_bn),
user_public_key: decode64(pad64(unpadded_p256dh)),
auth: decode64(pad64(unpadded_auth)))
key: payload.fetch(:shared_secret),
salt: payload.fetch(:salt),
server_public_key: payload.fetch(:server_public_key_bn),
user_public_key: decode64(pad64(unpadded_p256dh)),
auth: decode64(pad64(unpadded_auth)))

expect(decrypted_data).to eq("Hello World")
expect(decrypted_data).to eq('Hello World')
end

def generate_ecdh_key
group = "prime256v1"
group = 'prime256v1'
curve = OpenSSL::PKey::EC.new(group)
curve.generate_key
curve.public_key.to_bn.to_s(2)
Expand All @@ -73,9 +73,7 @@ def decode64(str)
end

def pad64(str)
if !str.end_with?("=") && str.length % 4 != 0
str = str.ljust((str.length + 3) & ~3, "=")
end
str = str.ljust((str.length + 3) & ~3, '=') if !str.end_with?('=') && str.length % 4 != 0
str
end
end
Expand Down

0 comments on commit c1d5dc5

Please sign in to comment.