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,82 @@
|
|
1
|
+
require_relative 'response'
|
2
|
+
require_relative 'error_response'
|
3
|
+
require_relative 'authentication_response'
|
4
|
+
require_relative 'refresh_response'
|
5
|
+
|
6
|
+
module Lapis
|
7
|
+
module Yggdrasil
|
8
|
+
module Messaging
|
9
|
+
|
10
|
+
# Parses messages from the server and builds responses from them.
|
11
|
+
class ResponseFactory
|
12
|
+
|
13
|
+
# Generates an instance from a response to an authentication request.
|
14
|
+
# @param message [HTTP::Message] Message received from the server.
|
15
|
+
# @return [AuthenticationResponse] Successful result from authentication.
|
16
|
+
# @return [ErrorResponse] Failure result from authentication.
|
17
|
+
# @see AuthenticationRequest
|
18
|
+
def parse_authentication_response(message)
|
19
|
+
if message.ok?
|
20
|
+
AuthenticationResponse.new(message)
|
21
|
+
else
|
22
|
+
ErrorResponse.new(message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generates an instance from a response to a refresh request.
|
27
|
+
# @param message [HTTP::Message] Message received from the server.
|
28
|
+
# @return [RefreshResponse] Successful result from refresh.
|
29
|
+
# @return [ErrorResponse] Failure result from refresh.
|
30
|
+
# @see RefreshRequest
|
31
|
+
def parse_refresh_response(message)
|
32
|
+
if message.ok?
|
33
|
+
RefreshResponse.new(message)
|
34
|
+
else
|
35
|
+
ErrorResponse.new(message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Generates an instance from a response to a validation request.
|
40
|
+
# @param message [HTTP::Message] Message received from the server.
|
41
|
+
# @return [Response] Successful result from validation.
|
42
|
+
# @return [ErrorResponse] Failure result from validation.
|
43
|
+
# @see ValidateRequest
|
44
|
+
def parse_validate_response(message)
|
45
|
+
if message.ok?
|
46
|
+
Response.new(message)
|
47
|
+
else
|
48
|
+
ErrorResponse.new(message)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Generates an instance from a response to an invalidation request.
|
53
|
+
# @param message [HTTP::Message] Message received from the server.
|
54
|
+
# @return [Response] Successful result from validation.
|
55
|
+
# @return [ErrorResponse] Failure result from validation.
|
56
|
+
# @see InvalidateRequest
|
57
|
+
def parse_invalidate_response(message)
|
58
|
+
if message.ok?
|
59
|
+
Response.new(message)
|
60
|
+
else
|
61
|
+
ErrorResponse.new(message)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generates an instance from a response to a signout request.
|
66
|
+
# @param message [HTTP::Message] Message received from the server.
|
67
|
+
# @return [Response] Successful result from signout.
|
68
|
+
# @return [ErrorResponse] Failure result from signout.
|
69
|
+
# @see SignoutRequest
|
70
|
+
def parse_signout_response(message)
|
71
|
+
if message.ok?
|
72
|
+
Response.new(message)
|
73
|
+
else
|
74
|
+
ErrorResponse.new(message)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'request'
|
3
|
+
|
4
|
+
module Lapis
|
5
|
+
module Yggdrasil
|
6
|
+
module Messaging
|
7
|
+
|
8
|
+
# Request for invalidating sessions without access tokens.
|
9
|
+
class SignoutRequest < Request
|
10
|
+
|
11
|
+
# Creates a new signout request.
|
12
|
+
# @param username [String] Minecraft username or Mojang account email address.
|
13
|
+
# @param password [String] Password for the user.
|
14
|
+
def initialize(username, password)
|
15
|
+
@username = username.dup.freeze
|
16
|
+
@password = password.dup.freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
# Path on the server that handles the request.
|
20
|
+
# @return [String] Path to handler.
|
21
|
+
def endpoint
|
22
|
+
'/signout'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generates a JSON string that can be sent to an authentication server.
|
26
|
+
# @return [String] JSON string.
|
27
|
+
def to_json
|
28
|
+
{
|
29
|
+
:username => @username,
|
30
|
+
:password => @password
|
31
|
+
}.to_json
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'request'
|
2
|
+
|
3
|
+
module Lapis
|
4
|
+
module Yggdrasil
|
5
|
+
module Messaging
|
6
|
+
|
7
|
+
# Type of request that includes access and client tokens.
|
8
|
+
# @abstract
|
9
|
+
class TokenRequest < Request
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Unique ID of the client that performed the authentication.
|
14
|
+
# @return [Uuid]
|
15
|
+
attr_reader :client_token
|
16
|
+
|
17
|
+
# Unique ID of the authentication session.
|
18
|
+
# @return [Uuid]
|
19
|
+
attr_reader :access_token
|
20
|
+
|
21
|
+
# Creates a new token request.
|
22
|
+
# @param client_token [Uuid] Unique ID Of the client that performed the authentication.
|
23
|
+
# @param access_token [Uuid] Unique ID of the authentication session.
|
24
|
+
def initialize(client_token, access_token)
|
25
|
+
@client_token = client_token
|
26
|
+
@access_token = access_token
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generates a structure that can be used for generating JSON.
|
30
|
+
# @return [Hash] Structure containing token information.
|
31
|
+
def structure
|
32
|
+
{
|
33
|
+
:accessToken => @access_token.to_s(false),
|
34
|
+
:clientToken => @client_token.to_s(false)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'lapis/uuid'
|
3
|
+
require_relative 'response'
|
4
|
+
|
5
|
+
module Lapis
|
6
|
+
module Yggdrasil
|
7
|
+
module Messaging
|
8
|
+
|
9
|
+
# Type of response that includes access and client tokens.
|
10
|
+
# @abstract
|
11
|
+
class TokenResponse < Response
|
12
|
+
|
13
|
+
# Unique ID of the client that performed the authentication.
|
14
|
+
# @return [Uuid]
|
15
|
+
attr_reader :client_token
|
16
|
+
|
17
|
+
# Unique ID of the authentication session.
|
18
|
+
# @return [Uuid]
|
19
|
+
attr_reader :access_token
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
# Creates a token response by parsing a response message.
|
24
|
+
# @param message [HTTP::Message] Message received from the server.
|
25
|
+
def initialize(message)
|
26
|
+
super(message)
|
27
|
+
hash = JSON.parse(message.body)
|
28
|
+
@client_token = Uuid.parse(hash['clientToken'])
|
29
|
+
@access_token = Uuid.parse(hash['accessToken'])
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
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 checking if a session is still valid.
|
9
|
+
class ValidateRequest < TokenRequest
|
10
|
+
|
11
|
+
# Creates a new validate 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
|
+
'/validate'
|
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,65 @@
|
|
1
|
+
require 'lapis/uuid'
|
2
|
+
|
3
|
+
module Lapis
|
4
|
+
module Yggdrasil
|
5
|
+
|
6
|
+
# Player information associated with a game.
|
7
|
+
class Profile
|
8
|
+
|
9
|
+
# Unique ID of the profile.
|
10
|
+
# @return [Uuid]
|
11
|
+
attr_reader :id
|
12
|
+
|
13
|
+
# Displayed player name.
|
14
|
+
# @return [String]
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# @!attribute [r] legacy?
|
18
|
+
# Indicates whether the user hasn't migrated to a Mojang account.
|
19
|
+
# @return [true] The profile has *not* been migrated to a Mojang account.
|
20
|
+
# @return [false] The profile has been migrated to a Mojang account.
|
21
|
+
def legacy?
|
22
|
+
@is_legacy
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a player profile.
|
26
|
+
# @param id [Uuid] Unique ID of the profile.
|
27
|
+
# @param name [String] Displayed player name.
|
28
|
+
# @param is_legacy [Boolean] +true+ if the profile hasn't been migrated to a Mojang account, or +false+ if it has.
|
29
|
+
def initialize(id, name, is_legacy = false)
|
30
|
+
@id = id
|
31
|
+
@name = name.dup.freeze
|
32
|
+
@is_legacy = !!is_legacy
|
33
|
+
end
|
34
|
+
|
35
|
+
# Compares the profile to another.
|
36
|
+
# @param other [Profile] Profile to compare against.
|
37
|
+
# @return [true] The profiles are the same.
|
38
|
+
# @return [false] The profiles are different.
|
39
|
+
def ==(other)
|
40
|
+
other.id == @id &&
|
41
|
+
other.name == @name &&
|
42
|
+
other.legacy? == @is_legacy
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates a profile from a set of properties in a response.
|
46
|
+
# @param properties [Hash<Symbol => String>] Set of properties.
|
47
|
+
# @option properties [String] :id Unique ID of the profile.
|
48
|
+
# @option properties [String] :name Displayed player name.
|
49
|
+
# @option properties [String] :legacy +true+ or +false+ indicating whether the user hasn't migrated to a Mojang account.
|
50
|
+
# @return [Profile]
|
51
|
+
# @raise [ArgumentError] The +:id+ or +:name+ property is missing.
|
52
|
+
def self.from_properties(properties)
|
53
|
+
fail ArgumentError, 'ID is required' unless properties.key?(:id)
|
54
|
+
fail ArgumentError, 'Name is required' unless properties.key?(:name)
|
55
|
+
|
56
|
+
id = Lapis::Uuid.parse(properties[:id])
|
57
|
+
name = properties[:name]
|
58
|
+
is_legacy = (properties.key?(:legacy) && properties[:legacy] == 'true')
|
59
|
+
new(id, name, is_legacy)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Lapis
|
2
|
+
module Yggdrasil
|
3
|
+
|
4
|
+
# Manages state information and transitioning for a session.
|
5
|
+
# @see SessionInfo
|
6
|
+
class Session
|
7
|
+
|
8
|
+
# Information about the current session.
|
9
|
+
# @return [SessionInfo]
|
10
|
+
attr_reader :info
|
11
|
+
|
12
|
+
# @!attribute [r] valid?
|
13
|
+
# Indicates whether the session is still valid on the server.
|
14
|
+
# @return [true] The session is valid.
|
15
|
+
# @return [false] The session has expired and shouldn't be used.
|
16
|
+
def valid?
|
17
|
+
@is_valid &&= validate
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates a new session.
|
21
|
+
# The session still needs to be authenticated after initialization.
|
22
|
+
# @param info [SessionInfo] Information about the current session.
|
23
|
+
# @param client [AuthenticationClient] Client used to perform authentication actions.
|
24
|
+
def initialize(info, client)
|
25
|
+
@info = info
|
26
|
+
@client = client
|
27
|
+
@is_valid = true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Refresh the session so it can be used again.
|
31
|
+
# @param profile [Profile, nil] Profile to switch to.
|
32
|
+
# Set to +nil+ to keep the current profile.
|
33
|
+
# @return [SessionInfo] Updated session information.
|
34
|
+
# @note Setting +profile+ to anything other than +nil+ will result in an error.
|
35
|
+
# In the current version of Yggdrasil, switching profiles is not supported.
|
36
|
+
# @raise [AuthenticationError] A problem occurred with authentication.
|
37
|
+
def refresh(profile = nil)
|
38
|
+
@info = @client.refresh(@info, profile)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks that the session is still valid.
|
42
|
+
# @return [true] The session is still valid.
|
43
|
+
# @return [false] The session is no longer valid.
|
44
|
+
# @raise [AuthenticationError] A problem occurred with authentication.
|
45
|
+
def validate
|
46
|
+
@client.validate(@info)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Marks the session as not valid.
|
50
|
+
# @return [void]
|
51
|
+
# @raise [AuthenticationError] A problem occurred with authentication.
|
52
|
+
def invalidate
|
53
|
+
@client.invalidate(@info)
|
54
|
+
@is_valid = false
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'lapis/uuid'
|
2
|
+
require_relative 'profile'
|
3
|
+
|
4
|
+
module Lapis
|
5
|
+
module Yggdrasil
|
6
|
+
|
7
|
+
# Token and profile information associated with an authentication session.
|
8
|
+
class SessionInfo
|
9
|
+
|
10
|
+
# Unique ID of the client that performed the authentication.
|
11
|
+
# @return [Uuid]
|
12
|
+
attr_reader :client_token
|
13
|
+
|
14
|
+
# Unique ID of the authentication session.
|
15
|
+
# @return [Uuid]
|
16
|
+
attr_reader :access_token
|
17
|
+
|
18
|
+
# Information about the currently selected player profile.
|
19
|
+
# @return [Profile]
|
20
|
+
attr_reader :profile
|
21
|
+
|
22
|
+
# Creates information about an authentication session.
|
23
|
+
# @param client_token [Uuid] Unique ID of the client that performed the authentication.
|
24
|
+
# @param access_token [Uuid] Unique ID of the authentication session.
|
25
|
+
# @param profile [Profile] Information about the currently selected player profile.
|
26
|
+
def initialize(client_token, access_token, profile)
|
27
|
+
@client_token = client_token
|
28
|
+
@access_token = access_token
|
29
|
+
@profile = profile
|
30
|
+
end
|
31
|
+
|
32
|
+
# Compares this session information to another.
|
33
|
+
# @param other [SessionInfo] Session information to compare against.
|
34
|
+
# @return [true] The session information is identical.
|
35
|
+
# @return [false] The session information is different.
|
36
|
+
def ==(other)
|
37
|
+
other.client_token == @client_token &&
|
38
|
+
other.access_token == @access_token &&
|
39
|
+
other.profile == @profile
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates information about an authentication session from response properties.
|
43
|
+
# @param properties [Hash] Set of properties.
|
44
|
+
# @option properties [String] :clientToken Unique ID of the client that performed the authentication.
|
45
|
+
# @option properties [String] :accessToken Unique ID of the authentication session.
|
46
|
+
# @option properties [Hash] :selectedProfile Information about the currently selected player profile.
|
47
|
+
# @return [SessionInfo] Information about an authentication session pulled from a set of properties.
|
48
|
+
def self.from_properties(properties)
|
49
|
+
fail ArgumentError, 'Client token is required' unless properties.key?(:clientToken)
|
50
|
+
fail ArgumentError, 'Access token is required' unless properties.key?(:accessToken)
|
51
|
+
fail ArgumentError, 'Selected profile is required' unless properties.key?(:selectedProfile)
|
52
|
+
|
53
|
+
client_token = Uuid.parse(properties[:clientToken])
|
54
|
+
access_token = Uuid.parse(properties[:accessToken])
|
55
|
+
profile = Profile.from_properties(properties[:selectedProfile])
|
56
|
+
new(client_token, access_token, profile)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|