pusher-platform 0.1.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 +7 -0
- data/lib/pusher-platform.rb +1 -0
- data/lib/pusher-platform/app.rb +69 -0
- data/lib/pusher-platform/authenticator.rb +136 -0
- data/lib/pusher-platform/base_client.rb +48 -0
- data/lib/pusher-platform/common.rb +4 -0
- data/lib/pusher-platform/error_response.rb +15 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d95d9cc5e0f7c22f8ca7408c9486fb2b464c0572
|
4
|
+
data.tar.gz: 6ea7d2d88d3242661f2a8d1bf4531034a979abaf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78c889277b27b346069674ec5555ed3169fde818a39a3c925600eeeb704171e79d47a767bf5662dec3fdb867c220f17e9d5239232f7f71e557ef26ad5ce65227
|
7
|
+
data.tar.gz: 53e074a29c83777a6d47dd346db179ecc68076eeca6da81a1e3cd0854a3dd639cc11f92104cfa0197d0f758fefaaab7927a2c0b6ef4611732808ff1a4f58baec
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'pusher-platform/app'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative './authenticator'
|
2
|
+
require_relative './base_client'
|
3
|
+
require_relative './common'
|
4
|
+
require_relative './error_response'
|
5
|
+
|
6
|
+
module Pusher
|
7
|
+
class App
|
8
|
+
def initialize(options)
|
9
|
+
raise "Invalid app ID" if options[:app_id].nil?
|
10
|
+
@app_id = options[:app_id]
|
11
|
+
|
12
|
+
app_key_parts = /^([^:]+):(.+)$/.match(options[:app_key])
|
13
|
+
raise "Invalid app key" if app_key_parts.nil?
|
14
|
+
|
15
|
+
@app_key_id = app_key_parts[1]
|
16
|
+
@app_key_secret = app_key_parts[2]
|
17
|
+
|
18
|
+
@client = if options[:client]
|
19
|
+
options[:client]
|
20
|
+
else
|
21
|
+
raise "Invalid cluster" if options[:cluster].nil?
|
22
|
+
BaseClient.new(host: options[:cluster])
|
23
|
+
end
|
24
|
+
|
25
|
+
@authenticator = Authenticator.new(@app_id, @app_key_id, @app_key_secret)
|
26
|
+
end
|
27
|
+
|
28
|
+
def request(options)
|
29
|
+
options = scope_request_options("apps", options)
|
30
|
+
if options[:jwt].nil?
|
31
|
+
options = options.merge({ jwt: generate_superuser_jwt() })
|
32
|
+
end
|
33
|
+
@client.request(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def config_request(options)
|
37
|
+
options = scope_request_options("config/apps", options)
|
38
|
+
if options[:jwt].nil?
|
39
|
+
options = options.merge({ jwt: generate_superuser_jwt() })
|
40
|
+
end
|
41
|
+
@client.request(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def authenticate(request, options)
|
45
|
+
@authenticator.authenticate(request, options)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def scope_request_options(prefix, options)
|
51
|
+
path = "/#{prefix}/#{@app_id}/#{options[:path]}"
|
52
|
+
.gsub(/\/+/, "/")
|
53
|
+
.gsub(/\/+$/, "")
|
54
|
+
options.merge({ path: path })
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_superuser_jwt
|
58
|
+
now = Time.now.utc.to_i
|
59
|
+
claims = {
|
60
|
+
app: @app_id,
|
61
|
+
iss: "keys/#{@app_key_id}",
|
62
|
+
su: true,
|
63
|
+
iat: now - 30, # some leeway for the server
|
64
|
+
exp: now + 60*5, # 5 minutes should be enough for a single request
|
65
|
+
}
|
66
|
+
JWT.encode(claims, @app_key_secret)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
module Pusher
|
5
|
+
TOKEN_LEEWAY = 30
|
6
|
+
TOKEN_EXPIRY = 24*60*60
|
7
|
+
|
8
|
+
class Authenticator
|
9
|
+
def initialize(app_id, app_key_id, app_key_secret)
|
10
|
+
@app_id = app_id
|
11
|
+
@app_key_id = app_key_id
|
12
|
+
@app_key_secret = app_id
|
13
|
+
end
|
14
|
+
|
15
|
+
# Takes a Rack request to the authorization endpoint and and handles it
|
16
|
+
# either returning a new access/refresh token pair, or an error.
|
17
|
+
#
|
18
|
+
# @param request [Rack::Request] the request to authenticate
|
19
|
+
# @return the response object
|
20
|
+
def authenticate(request, options)
|
21
|
+
form_data = Rack::Utils.parse_nested_query request.body.read
|
22
|
+
grant_type = form_data['grant_type']
|
23
|
+
|
24
|
+
if grant_type == "client_credentials"
|
25
|
+
return authenticate_with_client_credentials(options)
|
26
|
+
elsif grant_type == "refresh_token"
|
27
|
+
old_refresh_jwt = form_data['refresh_token']
|
28
|
+
return authenticate_with_refresh_token(old_refresh_jwt, options)
|
29
|
+
else
|
30
|
+
return response(401, {
|
31
|
+
error: "unsupported_grant_type"
|
32
|
+
})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def authenticate_with_client_credentials(options)
|
39
|
+
return respond_with_new_token_pair(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def authenticate_with_refresh_token(old_refresh_jwt, options)
|
43
|
+
old_refresh_token = begin
|
44
|
+
JWT.decode(old_refresh_jwt, @app_key_secret, true, {
|
45
|
+
iss: "keys/#{@app_key_id}",
|
46
|
+
verify_iss: true,
|
47
|
+
leeway: 30,
|
48
|
+
}).first
|
49
|
+
rescue => e
|
50
|
+
error_description = if e.is_a?(JWT::InvalidIssuerError)
|
51
|
+
"refresh token issuer is invalid"
|
52
|
+
elsif e.is_a?(JWT::ImmatureSignature)
|
53
|
+
"refresh token is not valid yet"
|
54
|
+
elsif e.is_a?(JWT::ExpiredSignature)
|
55
|
+
"refresh tokan has expired"
|
56
|
+
else
|
57
|
+
"refresh token is invalid"
|
58
|
+
end
|
59
|
+
|
60
|
+
return response(401, {
|
61
|
+
error: "invalid_grant",
|
62
|
+
error_description: error_description,
|
63
|
+
# TODO error_uri
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
if old_refresh_token["refresh"] != true
|
68
|
+
return response(401, {
|
69
|
+
error: "invalid_grant",
|
70
|
+
error_description: "refresh token does not have a refresh claim",
|
71
|
+
# TODO error_uri
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
if options[:user_id] != old_refresh_token["sub"]
|
76
|
+
return response(401, {
|
77
|
+
error: "invalid_grant",
|
78
|
+
error_description: "refresh token has an invalid user id",
|
79
|
+
# TODO error_uri
|
80
|
+
})
|
81
|
+
end
|
82
|
+
|
83
|
+
return respond_with_new_token_pair(options)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates a payload dictionary made out of access and refresh token pair and TTL for the access token.
|
87
|
+
#
|
88
|
+
# @param user_id [String] optional id of the user, ignore for anonymous users
|
89
|
+
# @return [Hash] Payload as a hash
|
90
|
+
def respond_with_new_token_pair(options)
|
91
|
+
access_token = generate_access_token(options)
|
92
|
+
refresh_token = generate_refresh_token(options)
|
93
|
+
return response(200, {
|
94
|
+
access_token: access_token,
|
95
|
+
token_type: "bearer",
|
96
|
+
expires_in: TOKEN_EXPIRY,
|
97
|
+
refresh_token: refresh_token,
|
98
|
+
})
|
99
|
+
end
|
100
|
+
|
101
|
+
def generate_access_token(options)
|
102
|
+
now = Time.now.utc.to_i
|
103
|
+
|
104
|
+
claims = {
|
105
|
+
app: @app_id,
|
106
|
+
iss: "keys/#{@app_key_id}",
|
107
|
+
iat: now - TOKEN_LEEWAY,
|
108
|
+
exp: now + TOKEN_EXPIRY + TOKEN_LEEWAY,
|
109
|
+
sub: options[:user_id],
|
110
|
+
}
|
111
|
+
|
112
|
+
JWT.encode(claims, @app_key_secret, "HS256")
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_refresh_token(options)
|
116
|
+
now = Time.now.utc.to_i
|
117
|
+
|
118
|
+
claims = {
|
119
|
+
app: @app_id,
|
120
|
+
iss: "keys/#{@app_key_id}",
|
121
|
+
iat: now - TOKEN_LEEWAY,
|
122
|
+
refresh: true,
|
123
|
+
sub: options[:user_id],
|
124
|
+
}
|
125
|
+
|
126
|
+
JWT.encode(claims, @app_key_secret, "HS256")
|
127
|
+
end
|
128
|
+
|
129
|
+
def response(status, body)
|
130
|
+
return {
|
131
|
+
status: status,
|
132
|
+
json: body,
|
133
|
+
}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'excon'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Pusher
|
5
|
+
class BaseClient
|
6
|
+
def initialize(options)
|
7
|
+
raise "Unspecified host" if options[:host].nil?
|
8
|
+
@connection = Excon.new("https://#{options[:host]}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def request(options)
|
12
|
+
raise "Unspecified request method" if options[:method].nil?
|
13
|
+
raise "Unspecified request path" if options[:path].nil?
|
14
|
+
|
15
|
+
headers = if options[:headers]
|
16
|
+
options[:headers].dup
|
17
|
+
else
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
|
21
|
+
if options[:jwt]
|
22
|
+
headers["Authorization"] = "Bearer #{options[:jwt]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
response = @connection.request(
|
26
|
+
method: options[:method],
|
27
|
+
path: options[:path],
|
28
|
+
headers: headers,
|
29
|
+
body: options[:body],
|
30
|
+
)
|
31
|
+
|
32
|
+
if response.status >= 200 && response.status <= 299
|
33
|
+
return response
|
34
|
+
elsif response.status >= 300 && response.status <= 399
|
35
|
+
raise "unsupported redirect response: #{response.status}"
|
36
|
+
elsif response.status >= 400 && response.status <= 599
|
37
|
+
error_body = begin
|
38
|
+
JSON.parse(response.body)
|
39
|
+
rescue
|
40
|
+
response.body
|
41
|
+
end
|
42
|
+
raise ErrorResponse.new(response.status, response.headers, error_body)
|
43
|
+
else
|
44
|
+
raise "unsupported response code: #{response.status}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Pusher
|
2
|
+
class ErrorResponse < Error
|
3
|
+
attr_accessor :status, :headers, :body
|
4
|
+
|
5
|
+
def initialize(status, headers, body)
|
6
|
+
@status = status
|
7
|
+
@headers = headers
|
8
|
+
@body = body
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"Pusher::ErrorResponse: #{status} #{body}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pusher-platform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pusher
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: excon
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.54.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.54.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jwt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.5.6
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.5'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.5.6
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rack
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.0'
|
61
|
+
description:
|
62
|
+
email: support@pusher.com
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- lib/pusher-platform.rb
|
68
|
+
- lib/pusher-platform/app.rb
|
69
|
+
- lib/pusher-platform/authenticator.rb
|
70
|
+
- lib/pusher-platform/base_client.rb
|
71
|
+
- lib/pusher-platform/common.rb
|
72
|
+
- lib/pusher-platform/error_response.rb
|
73
|
+
homepage:
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.5.2
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Pusher Platform Ruby SDK
|
97
|
+
test_files: []
|