Table of Contents
In the document Server Overview's section Design Introduction → Use Cases → Logging In, an overview of the login process is given. This chapter details the part of that process concerned with authenticate the login details and loading the initial entity.
By default, authentication of the player's username and password is done by the DBMgr. It also handles the mapping of that username to the initial entity the player will use. There are a number of different ways this can be done. This section discusses these different approaches.
When using the MySQL database, DBMgr uses the bigworldLogOnMapping table (for details, see Entity Tables) to check login's validity and which entity to load for the user. It contains the following columns:
-
logOnName
Username used to log in to BigWorld. This corresponds to the
username
argument passed to theBigWorld
.connect()
call on the client.This is the primary key.
-
password
User's password. This corresponds to the
argument passed to thepassword
BigWorld
.connect()
call on the client.If
bw.xml
file's billingSystem/isPasswordHashed setting is true, the password will be hashed. The following SQL is used to perform the hashing:MD5( CONCAT(
password
, logOnName ) ) -
entityType
Type ID of the entity to create. To map the type ID to its name, bigworldEntityTypes table (for details, see Entity Tables.) is used. This can be queried with the following MySQL query.
SELECT typeID FROM bigworldEntityTypes WHERE name='MyEntityType';
-
entityID
The database id of the associated entity.
To determine whether a login is valid and which entity to load, DBMgr executes the following steps:
-
Find a row in the bigworldLogOnMapping table where the logOnName column matches the username.
-
Check that the user's password matches the password of the found row.
-
Load the entity identified by typeID and recordName.
For this step to succeed, the entity must already exist in the database. For information on how to create an entity and write it to the database, see Reading and Writing Entities.
This table is usually populated using MySQL tools. An example
script, bigworld/tools/server/misc/add_account.py
,
implements a command line utility that can be used to create
accounts.
For information on how BigWorld can automatically populate this table, see Accepting All Users.
Note
It is highly recommended to use the MySQL database back-end for production environments. You should consider moving away from the XML database before implementing user authentication.
The XML database implements the equivalent of the bigworldLogOnMapping table in the section _BigWorldInfo/LogOnMapping. Each row is specified using a separate sub-section, as illustrated below:
<root> <_BigWorldInfo> <LogOnMapping> <item> <logOnName> John </logOnName> <password> acde1234 </password> <type> Account </type> <entityID> 7231 </entityID> </item> <item> <logOnName> Peter </logOnName> <password> zyxw9876 </password> <type> Avatar </type> <entityID> 7232 </entityID> </item> </LogOnMapping> </_BigWorldInfo> ... </root>
bigworldLogOnMapping table in XML
In the example above, two rows are specified. The tags correspond to the table columns as described below:
-
logOnName ‐ Corresponds to column: logOnName
-
password ‐ Corresponds to column: password
-
type ‐ Corresponds to column: typeID
-
entityID ‐ Corresponds to column: entityID
When specifying the type of the entity, the type name is used instead of the type ID used by the MySQL database.
Please note that
is only read and written to during startup and shutdown, respectively.
Therefore, it is not possible to update the
_BigWorldInfo/LogOnMapping section and have the
changes take effect while DBMgr is running.
<res>
/scripts/db.xml
It is often the case that user accounts are stored in an external system. This section describes how to bypass the bigworldLogOnMapping table and use your own system.
The easiest way to achieve this is via Python. When the DBMgr
checks whether to use Python by attempting to call
BWPersonality.connectToBillingSystem()
. If this exists
and returns a Python object, this object is used to perform user
authentication. The object should implement a
getEntityKeyForAccount
and optionally
setEntityKeyForAccount
.
class BillingSystem( object ): def getEntityKeyForAccount( username, password, clientAddr, response ): ... def setEntityKeyForAccount( username, password, entityType, entityID ): ... def connectToBillingSystem(): return BillingSystem()
Example excerpt from res/scripts/db/BWPersonality.py
The getEntityKeyForAccount
method
accepts username, password, client address and response object as
arguments. The username and password should be used for authentication
and an appropriate response should be returned by calling one of the
methods of the handler object.
The response argument has the following methods:
-
loadEntity( entityType, entityDatabaseID ) ‐ This should be called on successful authentication. It accepts two arguments, the type and database id of the entity to be loaded from the database and used by this user.
-
loadEntityByName( entityType, entityName, shouldCreateUnknown ) ‐ This is like loadEntity but takes the name (identifier string) of the entity instead of the database id. It also takes a boolean argument indicating whether to create a new entity if one with this name does not exist.
-
createNewEntity( entityType, shouldRemember ) ‐ This should be called when logging in should proceed but a new entity should be created instead of loading one from the database. It accepts two argument, the type of the entity to create and a boolean indicating whether this new entity should be saved in the database and in the billing system.
-
failureInvalidPassword() ‐ This should be called when authentication fails because of an invalid password.
-
failureNoSuchUser() ‐ This should be called when authentication fails because no such user exists.
You may not want to differentiate between receiving an invalid
password or non-existent user. If so, you should just consistently
return either failureNoSuchUser
or
failureInvalidPassword
on failure.
The setEntityKeyForAccount
is only
required if createNewEntity
is ever called
with shouldRemember
as
True. If so, this is called with the username and
password of the account and the entity type and entity id of the
entity to associate with that account.
FantasyDemo has a simple example where account information is
stored in an sqlite database. This is located in
fantasydemo/res/scripts/db/FantasyDemo.py
and
fantasydemo/res/scripts/db/sqlite_billing.py
.
Billing system integration can also be done via C++. A skeleton
billing system case is implemented in
bigworld/src/server/dbmgr/custom_billing_system.cpp
.
Integration should be done by filling out this class. To make use of
this class, USE_CUSTOM_BILLING_SYSTEM
needs to be set to
1
in
bigworld/src/server/dbmgr/Makefile
.
The support for Python billing system integration is implemented
in
bigworld/src/server/dbmgr/py_billing_system.cpp
.
This can be used as an example.
During development, it is often convenient to grant all users access to the server, without having to set up a bigworldLogOnMapping table.
This can be achieved by setting bw.xml
file's
billingSystem/shouldAcceptUnknownUsers[36] configuration option to true. With this
configuration, a default entity is created for the user if there is no
row matching the username in
bigworldLogOnMapping.
The type of the entity created is controlled by the bw.xml file's billingSystem/entityTypeForUnknownUsers option. Additionally, if billingSystem/shouldRememberUnknownUsers is set to true, then the entity created for the unknown user is saved in the database, and an entry is added in bigworldLogOnMapping. This effectively adds a new user into the system, and subsequent logins by the same user will be processed via the normal login process.
If a custom billing system is being used, it is up to its implementation whether these options are respected.
An alternative method for performing user authentication, and entity selection is to delegate to an account entity and perform the logic in its base entity script. An account entity is just another BigWorld entity that is implemented using Python script. The DBMgr can be configured to bypass its usual login processing and pass control over to the account entity.
To use this method, the following configuration options must be set:
-
billingSystem/entityTypeForUnknownUsersA ‐ Required value: Your account entity type
Reason for required value:
Type of the entity that will handle the login process.
For details on how to implement entities, see Directory Structure for Entity Scripting.
-
billingSystem/authenticateViaBaseEntityA ‐ Required value: true
Reason for required value:
This bypasses the DBMgr authentication and always creates a Proxy entity of the type indicated above. If this entity already exists in the database with its identifier equal to the current username, that data will be loaded otherwise a new entity will be created. New entities are not immediately added to the database. It is up to the base entity script to write only the appropriate entities with a call to
Base.writeToDB()
.Note
The name of an entity is a property tagged with the
<Identifier>
tag. For details, see The Identifier Tag.
A ‐ For details, see the document Server Operations Guide's section Server Configuration with bw.xml → DBMgr Configuration Options.
After a successful login:
-
On the client ‐ The account's __init__ method is called.
-
On the BaseApp ‐ The account's onEntitiesEnabled method is called.
There are a couple of approaches to retrieving the user's password:
-
On the BaseApp, if the account entity has a property called password, it is automatically set to the user's login password. And, of course, the account's name property will try to match the username.
-
Since the login password sent by the client using BigWorld.connect method is usually sent in clear text, it may be preferable to use an empty password for normal login processing. Then, after the account entity has been created, the client can send an encrypted password to the Proxy.
It is up to the account entity's implementation to verify the user's password. This is usually done by communicating with a third-party billing system. For details on how to communicate with a non-BigWorld process, see Non-Blocking Socket I/O Using Mercury. If the user's password is invalid, then the Proxy entity can destroy itself to disconnect the client.
[36] For details, see the document Server Operations Guide's section Server Configuration with bw.xml → DBMgr Configuration Options.