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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +18 -0
  3. data/.gitignore +151 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +4 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.md +16 -0
  9. data/README.md +130 -0
  10. data/Rakefile +28 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +7 -0
  13. data/lapis-yggdrasil.gemspec +35 -0
  14. data/lib/lapis/yggdrasil.rb +18 -0
  15. data/lib/lapis/yggdrasil/agent.rb +52 -0
  16. data/lib/lapis/yggdrasil/authentication_client.rb +106 -0
  17. data/lib/lapis/yggdrasil/authentication_error.rb +29 -0
  18. data/lib/lapis/yggdrasil/client.rb +38 -0
  19. data/lib/lapis/yggdrasil/error_codes.rb +43 -0
  20. data/lib/lapis/yggdrasil/messaging.rb +23 -0
  21. data/lib/lapis/yggdrasil/messaging/authentication_request.rb +50 -0
  22. data/lib/lapis/yggdrasil/messaging/authentication_response.rb +36 -0
  23. data/lib/lapis/yggdrasil/messaging/error_response.rb +74 -0
  24. data/lib/lapis/yggdrasil/messaging/invalidate_request.rb +34 -0
  25. data/lib/lapis/yggdrasil/messaging/refresh_request.rb +47 -0
  26. data/lib/lapis/yggdrasil/messaging/refresh_response.rb +29 -0
  27. data/lib/lapis/yggdrasil/messaging/request.rb +25 -0
  28. data/lib/lapis/yggdrasil/messaging/response.rb +33 -0
  29. data/lib/lapis/yggdrasil/messaging/response_factory.rb +82 -0
  30. data/lib/lapis/yggdrasil/messaging/signout_request.rb +38 -0
  31. data/lib/lapis/yggdrasil/messaging/token_request.rb +42 -0
  32. data/lib/lapis/yggdrasil/messaging/token_response.rb +36 -0
  33. data/lib/lapis/yggdrasil/messaging/validate_request.rb +34 -0
  34. data/lib/lapis/yggdrasil/profile.rb +65 -0
  35. data/lib/lapis/yggdrasil/session.rb +60 -0
  36. data/lib/lapis/yggdrasil/session_info.rb +62 -0
  37. data/lib/lapis/yggdrasil/version.rb +5 -0
  38. data/spec/factories/agent_factory.rb +10 -0
  39. data/spec/factories/authentication_error_factory.rb +14 -0
  40. data/spec/factories/message_factory.rb +111 -0
  41. data/spec/factories/profile_factory.rb +20 -0
  42. data/spec/factories/session_info_factory.rb +11 -0
  43. data/spec/factories/uuid_factory.rb +28 -0
  44. data/spec/lapis/yggdrasil/agent_spec.rb +103 -0
  45. data/spec/lapis/yggdrasil/authentication_client_spec.rb +200 -0
  46. data/spec/lapis/yggdrasil/authentication_error_spec.rb +42 -0
  47. data/spec/lapis/yggdrasil/messaging/authentication_request_spec.rb +61 -0
  48. data/spec/lapis/yggdrasil/messaging/authentication_response_spec.rb +63 -0
  49. data/spec/lapis/yggdrasil/messaging/error_response_spec.rb +164 -0
  50. data/spec/lapis/yggdrasil/messaging/invalidate_request_spec.rb +29 -0
  51. data/spec/lapis/yggdrasil/messaging/refresh_request_spec.rb +70 -0
  52. data/spec/lapis/yggdrasil/messaging/refresh_response_spec.rb +50 -0
  53. data/spec/lapis/yggdrasil/messaging/response_factory_spec.rb +130 -0
  54. data/spec/lapis/yggdrasil/messaging/response_spec.rb +36 -0
  55. data/spec/lapis/yggdrasil/messaging/signout_request_spec.rb +29 -0
  56. data/spec/lapis/yggdrasil/messaging/validate_request_spec.rb +29 -0
  57. data/spec/lapis/yggdrasil/profile_spec.rb +108 -0
  58. data/spec/lapis/yggdrasil/session_info_spec.rb +131 -0
  59. data/spec/lapis/yggdrasil/session_spec.rb +158 -0
  60. data/spec/spec_helper.rb +89 -0
  61. 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
@@ -0,0 +1,5 @@
1
+ module Lapis
2
+ module Yggdrasil
3
+ VERSION = '0.5.0'
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ FactoryGirl.define do
2
+ factory :agent, class: Lapis::Yggdrasil::Agent do
3
+ transient do
4
+ name 'Minecraft'
5
+ version 1
6
+ end
7
+
8
+ initialize_with { new(name, version) }
9
+ end
10
+ end