lapis-yggdrasil 0.5.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/.codeclimate.yml +18 -0
- data/.gitignore +151 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/LICENSE.md +16 -0
- data/README.md +130 -0
- data/Rakefile +28 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lapis-yggdrasil.gemspec +35 -0
- data/lib/lapis/yggdrasil.rb +18 -0
- data/lib/lapis/yggdrasil/agent.rb +52 -0
- data/lib/lapis/yggdrasil/authentication_client.rb +106 -0
- data/lib/lapis/yggdrasil/authentication_error.rb +29 -0
- data/lib/lapis/yggdrasil/client.rb +38 -0
- data/lib/lapis/yggdrasil/error_codes.rb +43 -0
- data/lib/lapis/yggdrasil/messaging.rb +23 -0
- data/lib/lapis/yggdrasil/messaging/authentication_request.rb +50 -0
- data/lib/lapis/yggdrasil/messaging/authentication_response.rb +36 -0
- data/lib/lapis/yggdrasil/messaging/error_response.rb +74 -0
- data/lib/lapis/yggdrasil/messaging/invalidate_request.rb +34 -0
- data/lib/lapis/yggdrasil/messaging/refresh_request.rb +47 -0
- data/lib/lapis/yggdrasil/messaging/refresh_response.rb +29 -0
- data/lib/lapis/yggdrasil/messaging/request.rb +25 -0
- data/lib/lapis/yggdrasil/messaging/response.rb +33 -0
- data/lib/lapis/yggdrasil/messaging/response_factory.rb +82 -0
- data/lib/lapis/yggdrasil/messaging/signout_request.rb +38 -0
- data/lib/lapis/yggdrasil/messaging/token_request.rb +42 -0
- data/lib/lapis/yggdrasil/messaging/token_response.rb +36 -0
- data/lib/lapis/yggdrasil/messaging/validate_request.rb +34 -0
- data/lib/lapis/yggdrasil/profile.rb +65 -0
- data/lib/lapis/yggdrasil/session.rb +60 -0
- data/lib/lapis/yggdrasil/session_info.rb +62 -0
- data/lib/lapis/yggdrasil/version.rb +5 -0
- data/spec/factories/agent_factory.rb +10 -0
- data/spec/factories/authentication_error_factory.rb +14 -0
- data/spec/factories/message_factory.rb +111 -0
- data/spec/factories/profile_factory.rb +20 -0
- data/spec/factories/session_info_factory.rb +11 -0
- data/spec/factories/uuid_factory.rb +28 -0
- data/spec/lapis/yggdrasil/agent_spec.rb +103 -0
- data/spec/lapis/yggdrasil/authentication_client_spec.rb +200 -0
- data/spec/lapis/yggdrasil/authentication_error_spec.rb +42 -0
- data/spec/lapis/yggdrasil/messaging/authentication_request_spec.rb +61 -0
- data/spec/lapis/yggdrasil/messaging/authentication_response_spec.rb +63 -0
- data/spec/lapis/yggdrasil/messaging/error_response_spec.rb +164 -0
- data/spec/lapis/yggdrasil/messaging/invalidate_request_spec.rb +29 -0
- data/spec/lapis/yggdrasil/messaging/refresh_request_spec.rb +70 -0
- data/spec/lapis/yggdrasil/messaging/refresh_response_spec.rb +50 -0
- data/spec/lapis/yggdrasil/messaging/response_factory_spec.rb +130 -0
- data/spec/lapis/yggdrasil/messaging/response_spec.rb +36 -0
- data/spec/lapis/yggdrasil/messaging/signout_request_spec.rb +29 -0
- data/spec/lapis/yggdrasil/messaging/validate_request_spec.rb +29 -0
- data/spec/lapis/yggdrasil/profile_spec.rb +108 -0
- data/spec/lapis/yggdrasil/session_info_spec.rb +131 -0
- data/spec/lapis/yggdrasil/session_spec.rb +158 -0
- data/spec/spec_helper.rb +89 -0
- metadata +269 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
|
3
|
+
module Lapis
|
4
|
+
module Yggdrasil
|
5
|
+
|
6
|
+
# Provides low-level interaction with an REST-ful server.
|
7
|
+
# @abstract
|
8
|
+
class Client
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# HTTP headers to send as part of requests.
|
13
|
+
HEADERS = {
|
14
|
+
'Content-Type' => 'application/json'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Creates a new interface to an authentication server.
|
20
|
+
# @param url [String] Base URL of the authentication server.
|
21
|
+
def initialize(url)
|
22
|
+
@url = url.dup.freeze
|
23
|
+
@client = HTTPClient.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sends a request to the server and waits for a response.
|
27
|
+
# @param message [Messaging::Request] Request to send.
|
28
|
+
# @return [HTTP::Message] Response received from the server.
|
29
|
+
def send_request(message)
|
30
|
+
url = @url + message.endpoint
|
31
|
+
body = message.to_json
|
32
|
+
@client.post(url, :body => body, :header => HEADERS)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Lapis
|
2
|
+
module Yggdrasil
|
3
|
+
|
4
|
+
# Constants describing a type of error.
|
5
|
+
# @see Error
|
6
|
+
module ErrorCodes
|
7
|
+
|
8
|
+
# The method specified in the request is not allowed for the resource identified by the request URI.
|
9
|
+
# Something other than a `POST` request was received.
|
10
|
+
METHOD_NOT_ALLOWED = :method_not_allowed
|
11
|
+
|
12
|
+
# The server has not found anything matches the request URI.
|
13
|
+
# Non-existing endpoint was called.
|
14
|
+
NOT_FOUND = :not_found
|
15
|
+
|
16
|
+
# Invalid credentials - account has migrated to Mojang account.
|
17
|
+
# Use e-mail as username.
|
18
|
+
ACCOUNT_MIGRATED = :account_migrated
|
19
|
+
|
20
|
+
# Invalid credentials - bad username or password.
|
21
|
+
INVALID_CREDENTIALS = :invalid_credentials
|
22
|
+
|
23
|
+
# Invalid credentials - too many login attempts with this username recently.
|
24
|
+
# @note Username and password may still be valid.
|
25
|
+
TOO_MANY_ATTEMPTS = :too_many_attempts
|
26
|
+
|
27
|
+
# Invalid token - provided access token is invalid.
|
28
|
+
INVALID_TOKEN = :invalid_token
|
29
|
+
|
30
|
+
# Access token already has a profile assigned.
|
31
|
+
# Selecting profiles isn't implemented yet.
|
32
|
+
PROFILE_NOT_SUPPORTED = :profile_not_supported
|
33
|
+
|
34
|
+
# Username and password was not provided.
|
35
|
+
NO_CREDENTIALS = :no_credentials
|
36
|
+
|
37
|
+
# The server received something other than a message body with JSON.
|
38
|
+
UNSUPPORTED_MEDIA_TYPE = :unsupported_media_type
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Lapis
|
2
|
+
module Yggdrasil
|
3
|
+
|
4
|
+
# Representation of requests and responses sent to and from the authentication server.
|
5
|
+
module Messaging
|
6
|
+
end
|
7
|
+
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative 'messaging/request'
|
12
|
+
require_relative 'messaging/token_request'
|
13
|
+
require_relative 'messaging/authentication_request'
|
14
|
+
require_relative 'messaging/refresh_request'
|
15
|
+
require_relative 'messaging/validate_request'
|
16
|
+
require_relative 'messaging/signout_request'
|
17
|
+
require_relative 'messaging/invalidate_request'
|
18
|
+
require_relative 'messaging/response'
|
19
|
+
require_relative 'messaging/error_response'
|
20
|
+
require_relative 'messaging/token_response'
|
21
|
+
require_relative 'messaging/authentication_response'
|
22
|
+
require_relative 'messaging/refresh_response'
|
23
|
+
require_relative 'messaging/response_factory'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'request'
|
3
|
+
|
4
|
+
module Lapis
|
5
|
+
module Yggdrasil
|
6
|
+
module Messaging
|
7
|
+
|
8
|
+
# Message sent to attempt to authenticate.
|
9
|
+
# @see AuthenticationResponse
|
10
|
+
class AuthenticationRequest < Request
|
11
|
+
|
12
|
+
# Creates a new authentication request.
|
13
|
+
# @param username [String] Minecraft username or Mojang account email address.
|
14
|
+
# @param password [String] Password for the user.
|
15
|
+
# @param agent [Agent] Game information being requested.
|
16
|
+
# @param client_token [Uuid, nil] ID for the client instance.
|
17
|
+
# Provide +nil+ to have the authentication server provide one.
|
18
|
+
def initialize(username, password, agent, client_token = nil)
|
19
|
+
@username = username.dup.freeze
|
20
|
+
@password = password.dup.freeze
|
21
|
+
@agent = agent
|
22
|
+
@client_token = client_token
|
23
|
+
end
|
24
|
+
|
25
|
+
# Path on the server that handles the request.
|
26
|
+
# @return [String] Path to handler.
|
27
|
+
def endpoint
|
28
|
+
'/authenticate'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generates a JSON string that can be sent to an authentication server.
|
32
|
+
# @return [String] JSON string.
|
33
|
+
def to_json
|
34
|
+
hash = {
|
35
|
+
:agent => {
|
36
|
+
:name => @agent.name,
|
37
|
+
:version => @agent.version
|
38
|
+
},
|
39
|
+
:username => @username,
|
40
|
+
:password => @password
|
41
|
+
}
|
42
|
+
hash[:clientToken] = @client_token.to_s(false) if @client_token
|
43
|
+
hash.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'token_response'
|
3
|
+
require_relative '../profile'
|
4
|
+
|
5
|
+
module Lapis
|
6
|
+
module Yggdrasil
|
7
|
+
module Messaging
|
8
|
+
|
9
|
+
# Response to a an authentication request.
|
10
|
+
# @see AuthenticationRequest
|
11
|
+
class AuthenticationResponse < TokenResponse
|
12
|
+
|
13
|
+
# List of profiles to choose from.
|
14
|
+
# @return [Array<Profile>]
|
15
|
+
attr_reader :available_profiles
|
16
|
+
|
17
|
+
# Currently active profile for the user's selected game.
|
18
|
+
# @return [Profile]
|
19
|
+
attr_reader :selected_profile
|
20
|
+
|
21
|
+
# Creates a new authentication response by parsing a message from the server.
|
22
|
+
# @param message [HTTP::Message] Authentication message from the server.
|
23
|
+
def initialize(message)
|
24
|
+
super(message)
|
25
|
+
hash = JSON.parse(message.body, :symbolize_names => true)
|
26
|
+
@available_profiles = hash[:availableProfiles].map do |properties|
|
27
|
+
Profile.from_properties(properties)
|
28
|
+
end.freeze
|
29
|
+
@selected_profile = Profile.from_properties(hash[:selectedProfile])
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'response'
|
3
|
+
require_relative '../authentication_error'
|
4
|
+
require_relative '../error_codes'
|
5
|
+
|
6
|
+
module Lapis
|
7
|
+
module Yggdrasil
|
8
|
+
module Messaging
|
9
|
+
|
10
|
+
# Response to a request indicating a problem occurred.
|
11
|
+
class ErrorResponse < Response
|
12
|
+
|
13
|
+
# Keywords describing the problem.
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :error
|
16
|
+
|
17
|
+
# Brief description of the problem.
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :message
|
20
|
+
|
21
|
+
# Additional information about the root of the problem.
|
22
|
+
# @return [String] Additional information.
|
23
|
+
# @return [nil] No additional information available.
|
24
|
+
attr_reader :cause
|
25
|
+
|
26
|
+
# Creates a new error response by parsing a message from the server.
|
27
|
+
# @param message [HTTP::Message] Error message from the server.
|
28
|
+
def initialize(message)
|
29
|
+
super(message)
|
30
|
+
hash = JSON.parse(message.body)
|
31
|
+
@error = hash['error']
|
32
|
+
@message = hash['errorMessage']
|
33
|
+
@cause = hash['cause']
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a throwable exception from the response information.
|
37
|
+
# @return [AuthenticationError]
|
38
|
+
def to_error
|
39
|
+
code = case @error
|
40
|
+
when 'Method Not Allowed'
|
41
|
+
ErrorCodes::METHOD_NOT_ALLOWED
|
42
|
+
when 'Not Found'
|
43
|
+
ErrorCodes::NOT_FOUND
|
44
|
+
when 'ForbiddenOperationException'
|
45
|
+
if @cause == 'UserMigratedException'
|
46
|
+
ErrorCodes::ACCOUNT_MIGRATED
|
47
|
+
else
|
48
|
+
case @message
|
49
|
+
when /username/
|
50
|
+
ErrorCodes::INVALID_CREDENTIALS
|
51
|
+
when /credentials/
|
52
|
+
ErrorCodes::TOO_MANY_ATTEMPTS
|
53
|
+
when /token/
|
54
|
+
ErrorCodes::INVALID_TOKEN
|
55
|
+
end
|
56
|
+
end
|
57
|
+
when 'IllegalArgumentException'
|
58
|
+
case @message
|
59
|
+
when /profile/
|
60
|
+
ErrorCodes::PROFILE_NOT_SUPPORTED
|
61
|
+
when /credentials/
|
62
|
+
ErrorCodes::NO_CREDENTIALS
|
63
|
+
end
|
64
|
+
when 'Unsupported Media Type'
|
65
|
+
ErrorCodes::UNSUPPORTED_MEDIA_TYPE
|
66
|
+
end
|
67
|
+
AuthenticationError.new(code, @message)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'token_request'
|
3
|
+
|
4
|
+
module Lapis
|
5
|
+
module Yggdrasil
|
6
|
+
module Messaging
|
7
|
+
|
8
|
+
# Request for ending a session.
|
9
|
+
class InvalidateRequest < TokenRequest
|
10
|
+
|
11
|
+
# Creates a new invalidate request.
|
12
|
+
# @param client_token [Uuid] Unique ID Of the client that performed the authentication.
|
13
|
+
# @param access_token [Uuid] Unique ID of the authentication session.
|
14
|
+
def initialize(client_token, access_token)
|
15
|
+
super(client_token, access_token)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Path on the server that handles the request.
|
19
|
+
# @return [String] Path to handler.
|
20
|
+
def endpoint
|
21
|
+
'/invalidate'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generates a JSON string that can be sent to an authentication server.
|
25
|
+
# @return [String] JSON string.
|
26
|
+
def to_json
|
27
|
+
structure.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'token_request'
|
3
|
+
|
4
|
+
module Lapis
|
5
|
+
module Yggdrasil
|
6
|
+
module Messaging
|
7
|
+
|
8
|
+
# Request for renewing a previously used authentication session.
|
9
|
+
# @see RefreshResponse
|
10
|
+
class RefreshRequest < TokenRequest
|
11
|
+
|
12
|
+
# Creates a new refresh request.
|
13
|
+
# @param client_token [Uuid] Unique ID Of the client that performed the authentication.
|
14
|
+
# @param access_token [Uuid] Unique ID of the authentication session.
|
15
|
+
# @param profile [Profile, nil] New profile to switch to.
|
16
|
+
# Specify +nil+ to keep the same profile.
|
17
|
+
def initialize(client_token, access_token, profile = nil)
|
18
|
+
super(client_token, access_token)
|
19
|
+
@profile = profile
|
20
|
+
end
|
21
|
+
|
22
|
+
# Path on the server that handles the request.
|
23
|
+
# @return [String] Path to handler.
|
24
|
+
def endpoint
|
25
|
+
'/refresh'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generates a JSON string that can be sent to an authentication server.
|
29
|
+
# @return [String] JSON string.
|
30
|
+
def to_json
|
31
|
+
hash = structure
|
32
|
+
if @profile
|
33
|
+
profile_hash = {
|
34
|
+
:name => @profile.name,
|
35
|
+
:id => @profile.id.to_s(false)
|
36
|
+
}
|
37
|
+
profile_hash[:legacy] = true if @profile.legacy?
|
38
|
+
hash[:selectedProfile] = profile_hash
|
39
|
+
end
|
40
|
+
hash.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'token_response'
|
3
|
+
require_relative '../profile'
|
4
|
+
|
5
|
+
module Lapis
|
6
|
+
module Yggdrasil
|
7
|
+
module Messaging
|
8
|
+
|
9
|
+
# Response to a session renewal (refresh) request.
|
10
|
+
# @see RefreshRequest
|
11
|
+
class RefreshResponse < TokenResponse
|
12
|
+
|
13
|
+
# Profile selected for the user.
|
14
|
+
# @return [Profile]
|
15
|
+
attr_reader :profile
|
16
|
+
|
17
|
+
# Creates a new refresh response by parsing a message received from the server.
|
18
|
+
# @param message [HTTP::Message] Refresh response from the server.
|
19
|
+
def initialize(message)
|
20
|
+
super(message)
|
21
|
+
hash = JSON.parse(message.body, :symbolize_names => true)
|
22
|
+
@profile = Profile.from_properties(hash[:selectedProfile])
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Lapis
|
2
|
+
module Yggdrasil
|
3
|
+
module Messaging
|
4
|
+
|
5
|
+
# Base class for all request messages.
|
6
|
+
# @abstract
|
7
|
+
class Request
|
8
|
+
|
9
|
+
# Path on the server that handles the request.
|
10
|
+
# @return [String] Path to handler.
|
11
|
+
def endpoint
|
12
|
+
fail NotImplementedError, 'Sub-class must implement this method'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Generates a JSON string that can be sent to an authentication server.
|
16
|
+
# @return [String] JSON string.
|
17
|
+
def to_json
|
18
|
+
fail NotImplementedError, 'Sub-class must implement this method'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Lapis
|
2
|
+
module Yggdrasil
|
3
|
+
module Messaging
|
4
|
+
|
5
|
+
# Base class for all response messages received from the server.
|
6
|
+
# @abstract
|
7
|
+
class Response
|
8
|
+
|
9
|
+
# HTTP status code given in the response.
|
10
|
+
# @return [Fixnum] HTTP status code.
|
11
|
+
attr_reader :status
|
12
|
+
|
13
|
+
# Determines whether the result of the request was a success.
|
14
|
+
# @return [true] The result was a success.
|
15
|
+
# @return [false] The result was a failure.
|
16
|
+
def ok?
|
17
|
+
@is_ok
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
# Creates the base of the response by parsing a response message.
|
23
|
+
# @param message [HTTP::Message] Message received from the server.
|
24
|
+
def initialize(message)
|
25
|
+
@status = message.status
|
26
|
+
@is_ok = message.ok?
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|