New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Client identity storage service #2965
Client identity storage service #2965
Conversation
For more information about the identity storage service, please see: http://forum.terasology.org/threads/client-identity-cloud-storage-service.1846/#post-14953 This commit by itself is not adding ready-to-use features, but I prefer to break here since a not-so-small refactoring needs to be done before proceeding.
This is used in the login form.
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good PR! Just a few minor things:
Class names should be camel case: GET -> Get and POST -> Post
No wildcard imports, please.
|
||
@Override | ||
public int hashCode() { | ||
return playerPrivateCertificate.hashCode() ^ playerPrivateCertificate.hashCode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this will always be zero. Maybe just just 31 * playerPublicCertificate
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wanted to XOR the public with the private but accidentally typed private two times. I just found there is an Objects.hash for this purpose of building hashcodes for objects composed by multiple fields, would that be OK? Will change again if you prefer the manual implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also changed the hashCode() implementation in PrivateIdentityCertificate
to use this method. I'm not sure if I should change it in PlayerPublicCertificate
too since an implementation was already there (I didn't added it in this PR) which simply returns signature.hashCode()
; combining with the other fields (id, modulus and exponent) will probably reduce the collision rate but could also slow down the computation of the hash, what do you think about it?
/** | ||
* Represents an interaction that can be made with the storage service server. | ||
*/ | ||
abstract class Action { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either make it an interface or use Consumer<StorageServiceWorker>
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting point, making it an interface. In both cases I need to make the method public, however shouldn't be a big deal since both the interface and all the implementor classes are package-private and references to those classes never "leak" outside the package, right?
|
||
@Override | ||
public BigInteger deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | ||
return new BigInteger(DECODER.decode(json.getAsString().replace("\n", ""))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why could there be newline chars. in the data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it looks ugly, the problem is that the PostgreSQL base64 encoder used on the server inserts some newlines every a certain numbers of chars. I'll see if I can strip them on the server instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind
/** | ||
* Http methods; only the relevant ones for now but can easily be extended with the other standard ones. | ||
*/ | ||
enum HttpMethod { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe complete the list and make it public? There are not so many HTTP Methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, adding all the methods according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
*/ | ||
final class ServiceAPIRequest { | ||
|
||
private static final Gson GSON = new GsonBuilder().registerTypeHierarchyAdapter(BigInteger.class, BigIntegerBase64Serializer.getInstance()).create(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you reuse one of the existing instances of GSON? Not sure if it's possible or makes sense ..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, I searched for all the usages of GsonBuilder in the project and looks like that all the classes which need JSON serialization make their own instance with the appropriate type adapters.
} | ||
status = StorageServiceWorkerStatus.WORKING; | ||
logger.info("Performing action {}", action.getClass().getSimpleName()); | ||
new Thread(() -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please provide a Runnable to the Thread instead of subclassing it. I hope we have a shared thread pool executor soon, so you can just throw it there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please forgive me but I don't understand what you mean here, I'm providing an anonymous implementation of Runnable to the Thread's constructor, not subclassing Thread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sorry, I misread.
return buttonEnabled; | ||
} | ||
|
||
public String getLocalizedStatusMessage(TranslationSystem translationSystem, String loginName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those two methods look strange to me. I would move it either to a static helper class or - even better - to the (single?) caller class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving to static helper class since getLocalizedStatusMessage
is called in two places (PlayerSettingsScreen
and MainMenuScreen
)
import org.terasology.network.ClientComponent; | ||
import org.terasology.registry.In; | ||
import org.terasology.rendering.nui.NUIManager; | ||
|
||
@RegisterSystem | ||
public class ConsoleSystem extends BaseComponentSystem { | ||
|
||
private static final ResourceUrn MINICHAT_UI = new ResourceUrn("engine:minichatOverlay"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is that here now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used the MiniChatOverlay to show notifications from the storage service in the menus. If I understand correctly in the menu state there is no ChatSystem
so I made some modifications to ConsoleSystem
to show certain notifications in MiniChatOverlay
. In my opinion it's a good way to notify user when, for example, the synchronization is completed - since it happens asynchronously, I thought that showing message boxes may not be a good idea since they would pop up at random moments possibly annoying the user.
Let me now if something needs to be changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting idea - I like it. The word "chat" could be confusing though. Maybe we can find a better name... NotificationOverlay
maybe? Does not need to happen in this PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, the name "chat" would not be consistent anymore. I did the refactoring anyway in the latest commit since it was just a 5 minute task.
@@ -82,6 +82,9 @@ | |||
@LayoutConfig | |||
protected boolean readOnly; | |||
|
|||
@LayoutConfig | |||
private boolean passwordMode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many UI frameworks have a separate class (derived from the default text control). Have you thought about creating a UIPasswordText instead? It could be too much duplication, though ..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes considered that, but avoided for
too much duplication
exactly this reason. Can move to this approach if you prefer, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair enough. Thanks for clarifying
@@ -177,6 +180,15 @@ public UIText(String id) { | |||
cursorTexture = Assets.getTexture("engine:white").get(); | |||
} | |||
|
|||
private String buildPasswordString() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use this snippet if you like
char[] arr = new char[33];
Arrays.fill(arr, '*');
String str = String.valueOf(arr);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to figure out a better approach than using the StringBuilder but nothing came to my mind. Will now replace with your snippet, thanks.
Thank you for the review! Left some comments and made a commit with various fixes. |
Hooray Jenkins reported success with all tests good! |
Reason: the changes proposed in MovingBlocks#2965 use this widget to show messages from the identity storage service too.
Uh oh, something went wrong with the build. Need to check on that |
Reason: the changes proposed in MovingBlocks#2965 use this widget to show messages from the identity storage service too.
3470168
to
4ed1039
Compare
Hooray Jenkins reported success with all tests good! |
Previously, the instance of this class was being registered in the main menu state's context, leading to NullPointerException when trying to access the player settings screen from the in-game pause-menu.
Hooray Jenkins reported success with all tests good! |
I added one more commit to fix a problem: trying to open the player's settings screen from the in-game pause menu was throwing a NullReferenceException due to the StorageServiceWorker instance (which the player settings screen polls) was being registered in the main menu's context, and thus not accessible from the in-game context. I apologize if there is something bad in the fact that I added code that nobody asked for after the PR had been approved, possibly making the review/merge process longer, but I think this is still better than letting buggy code to propagate to other branches (develop/master). Unfortunately I didn't noticed this behavior when I tested before opening the PR because I didn't think about opening the settings from the pause menu. Anyway, now it's fixed: the StorageServiceWorker instance is registered in the rootContext and thus is more decoupled from engine and user interface's states, and the player settings screen can be accessed both from the main menu and from the in-game pause menu. |
Tried testing this out, but the server you're hosting seemingly took my registration (again I guess, tried earlier but the site looks much better now) then never sent me a confirmation key. Is it still working? :-) Also wondering where we should host it. Since our Weblate instance runs out of Docker already maybe we could just add a couple more containers or something? Poking @qwc and @msteiger for ideas Game itself seemed to run fine and I was able to test it successfully in the past! |
@Cervator : it's OK that it allowed you to "register again" after the previous time you registered because I rebuilt the database about a week ago when I added email verification and reCAPTCHA (the only accounts were mine and your tests, so I guessed it wasn't a big deal; anyway, I have a dump). I had a look at the database now and saw you made an account with email address which is waiting for confirmation. I tried to register another test account with my email and it worked, I got the activation mail - in the spam folder, however. Did you check yours? The reason it goes into spam, I guess, is that it doesn't come from a well-known/trusted mail service, but from a local SMTP server installed on the VPS, so Gmail thinks it's suspicious. |
Can we merge this one, @Cervator? |
@msteiger certainly - checked it out again and rested, got the confirmation email via junk mail, confirmed, and was able to validate the identity carried over :-) You don't need to wait for me if you have approved and feel you've tested decently, in that case merge ahead and just assign the PR (and any related issues) to the current milestone Great work @gianluca-nitti ! One small suggestion: Make the UI element a little more consistent between different screens - the little thing in the top right is only visible on the main menu? Maybe it should be there on the settings screen etc too? We should probably also find some places to update the docs, and a good on-going way to host the service. Will bump the forum thread :-) Thanks again! |
@Cervator Of course, spinning up docker containers is the easiest. That's the fancy thing with docker. |
Contains
The client to synchronize with a client identity storage service, as discussed here in the forum.
How to test
An implementation of the backend server is available here. You can install and configure it locally as described here, or use the public instance I'm hosting here.
To test, register an account on a server by navigating to it's address with a browser (or directly clicking the link above if you use the hosted instance). Then, start the game client (from this branch) and login by going to Settings > Player Settings. Now you can join a game server and as soon as you do, the new identity will be uploaded to the service; you can then start another client with empty/different configuration (e.g. use the 2nd or 3rd client run configurations in IntelliJ, or specify another data directory from the command line) and login into it too; your client identities will be synchronized and if you join the same game server, you will find your character in the same position you left it from the other client.
Outstanding before merging
Marking as WIP since I think there is still some minor improvement that can be done, e.g. localization of some UI messages. Also, the conflict resolving feature (which allows you which identity you want to keep for a certain server in case the locally stored one conflicts with the one stored on the service) is not fully working but this is due to a server issue which doesn't allow to overwrite the identities (will fix it soon).Update: I just added the localization feature (the strings are in the menu translations files; the values are filled for English only but can be easily be added for the other languages via editing the proper files or - I think - with Weblate if this branch is merged). Also fixed the server to support replacing of already uploaded identities.
Tagging @msteiger since we talked about this recently on appear.in.