This is a user management component for a micro-component application, supporting Viva con Agua in organizing volunteering activities. The component is based on the Play framework, Silhouette and the dwPlayDemo, implementing a basic user management. The authentication will be explained by Pablo Pedemonte from IBM. It provides:
Drops can be deployed using Docker. The drops.informatik.hu-berlin.de
config file (in conf/
directory) contains all configuration information needed
to run the service on production. Additionally, the following BASH scripts
should be used:
#!/bin/bash
docker pull mongo
docker pull cses/drops:0.9.0
(install.sh)
#!/bin/bash
docker run --name drops-mongo --restart=unless-stopped -d mongo
docker run --name drops --link drops-mongo:mongo --restart=unless-stopped -v $(pwd)/drops.informatik.hu-berlin.de.p12:/certs.p12 -h drops.informatik.hu-berlin.de -p 443:9443 cses/drops:0.9.0 \
-Dconfig.resource=drops.informatik.hu-berlin.de.conf \
-Dhttps.port=9443 \
-Dhttps.address=drops.informatik.hu-berlin.de \
-Dhttp.address=drops.informatik.hu-berlin.de \
-Dhttp.port=disabled \
-Dplay.server.https.keyStore.path=/certs.p12 \
-Dplay.server.https.keyStore.type=PKCS12 \
-Dplay.server.https.keyStore.password=61aca05eb0eb07c4c0c53f35b7edf3e1 \
-Dmongodb.uri=mongodb://mongo/drops \
-J-Xms128M -J-Xmx512m -J-server \
> server-output 2>&1 &
docker pull mariadb:latest
docker run --name drops-mariadb \
-e MYSQL_ROOT_PASSWORD=admin \
-e MYSQL_DATABASE=drops \
-e MYSQL_USER=drops \
-e MYSQL_PASSWORD=STRONG_PASSWORD \
-d mariadb
(start.sh)
#!/bin/bash
docker stop drops-mongo
docker stop drops-mariadb
docker stop drops
(stop.sh)
#!/bin/bash
docker rm drops-mongo
docker rm drops-mariadb
docker rm drops
(remove.sh)
Notice: All server generated output will be written to the
server-output
file. This is also needed to confirm users since the production server also uses a mock mail server.
After the default Play 2 App production deployment, the system requires the call of the route /auth/init
. This call creates a default admin account using a configured Email and Password. Both can be changed inside the admin.conf.
Additionally the same can be done for crews. The route that should be used is /crews/init
.
You can add a specific URL to the configuration that will be used for redirect after login. By default routes.Application.index
is used, but you can add:
login.flow {
ms.switch=true
ms.url=/pool/
}
to your application.conf
. ms.url
can hold every valid URL (absolute and relative).
Using an admin account test users can be generated. For generating the following
route has to be called: /users/init/:count/:countSpecialRoles
,
where :count
has to be replaced by the count of new users that
should be generated and :countSpecialRoles
by the number of users
with special roles (next to "Supporter").
Currently, the generation does not uses bulk inserts, so an insert operation will be executed for each test user. As a consequence, the system needs a lot of time.
The Drops service implements a webservice for requesting users and crews. Users will be described by the following JSON that is also returned to a valid request:
{
"id": "f2329fe0-c94b-4b33-a039-296c1a7dcba6",
"profiles": [
{
"loginInfo": {
"providerID": "credentials",
"providerKey": "test@test.com"
},
"primary": true,
"confirmed": true,
"email": "test@test.com",
"supporter": {
"firstName": "Tester",
"lastName": "Tester",
"fullName": "Tester Tester",
"mobilePhone": "0000/0000000",
"placeOfResidence": "Hamburg",
"birthday": 315529200000,
"sex": "male",
"crew": {
"crew": {
"name": "Berlin",
"country": "DE",
"cities": [
"Berlin"
]
},
"active": true
},
"pillars": [
{
"pillar": "operation"
},
{
"pillar": "finance"
}
]
}
}
],
"roles": [
{
"role": "supporter"
}
]
}
A user consists of an ID, multiple profiles and multiple roles. Drops implements different ways to create a virtual representation for a user. So he or she could
use default credentials or an existing Google or Facebook account. Also the users could connect their Drops accounts to Google or Facebook Accounts. In order to
establish which profile should be used, the primary
flag marks the so called profile. Additionally, it is possible to detect if an user has confirmed
his or her account using the confirmation mail after the sign up (confirmed
flag).
Next to the master data associated to the Supporter
, there are two information specific to Viva con Agua:
operation
- dt. "Aktionen", finance
, education
and network
)Currently, four different roles are implemented: supporter
(default role), volunteerManager
, employee
and admin
.
Supporters are all volunteering people of Viva con Agua, while a volunteer manager is a supporter that coordinates a crew. So, these roles are connected and one
crew can have multiple volunteer managers. An employee is not volunteering, but works for Viva con Agua and coordinates all entities inside the social system
(means crews and supporter).
Contrary to the other roles the admin is more technical. Users holding this role are able to access all possible configurations of the system.
Also the webservice will describe a crew by the following JSON:
{
"id": "4e899ba5-2897-4500-acdc-7ce998b033db",
"name": "Berlin",
"country": "DE",
"cities": [
"Berlin"
]
}
A crew has a name, a country code (described using the 2-Alpha codes of the ISO 3166-1) and a set of cities. Maybe there are regions with a lot of small cities or towns and a working infrastructure, where multiple volunteers in different cities join to one crew.
There are three entry points implemented:
/rest/users
: Returns a JSON containing a list of requested users/rest/users/:id
: Returns a JSON containing the user identified by the given ID/rest/crews
: Returns a JSON containing a list of requested crews There are three query parameter those have to be defined:
client_id
: Your service has to be registered at the Drops service. Use the registered ID.client_secret
: Your service has to be registered at the Drops service. Use the registered Secret.version
or v
: The version of the service that your request assumes. It is an optional
parameter. If nothing is defined, the latest version will be used.Currently, there are two different versions of the webservice:
1.0.0
: supports filtering by page, search and group and sorting1.1.0
: supports all features of version 1.0.0
. Additionally,
this version supports the all
filter: Returns all requested entities and ignores
a given pagination filter. So, if all
is true and no search or group filter is
defined all saved entities will be returned.All the mentioned above entry points are routes using the HTTP method POST
and the body of these requests can contain a query JSON like the following example (Version 1.0.0):
{
"filterBy" : {
"page" : {
"lastId": "f2329fe0-c94b-4b33-a039-296c1a7dcba6",
"countsPerPage": 100
},
"search" : [
{
"keyword" : "Berlin",
"fields": ["profiles.supporter.crew.crew.name"]
},
{
"keyword" : "Test",
"fields": ["profiles.supporter.firstName", "profiles.supporter.lastName"]
}
],
"groups" : [
{
"groupName" : "supporter",
"area" : { "name" : "role" }
},
{
"groupName" : "finance",
"area" : { "name" : "pillar" }
}
]
},
"sortBy" : [
{
"field": "profiles.supporter.firstName",
"dir" : "asc"
}
]
}
If the crews webservice is requested, the
groups
filter will be ignored. Additionally, thelastId
inside thepage
filter can be used for the crews name.
Since version 1.1.0 the following addional parameter is allowed:
{
"filterBy" : {
"all" : true
}
}
all
is a boolean parameter and it's also optional. If this parameter is set to true a possibly givenpage
filter will be ignored.
The filterBy
JSON block reduces the resulting set. It is possible to reduce the set using a pagination (lastId
is optional and if given the object with this ID won't be returned),
a search query (Regex based - it found every object that contains the given keyword as a substring of it's value inside one of the given fields) and groups (returns each object that is part of all given groups).
Using the sortBy
list of fields the results can be ordered. The sorting criteria will be applied in the given order.
Note: Every
POST
request to an entry points has to set the HTTP headerContent-Type: application/json
Drops allows other services to intiate an OAuth 2 handshake. The service implements
an OAuth 2 server and allows authorization_code
and password
based authentication. Assuming that all other services are part of the official
infrastructure, Drops implements a small addition to the OAuth 2 standard: The
authorization_code
authentication does not require additional permission
by the user.
Basically, your service will make an HTTP redirect to the Drops service, so Drops can check if there exists a session for the redirected client. If there exists no session until now, Drops will show a login screen and the can access using his or her default credentials. Otherwise Drops will generate an authorization code (based on the assumption that all registered services are part of the official infrastructure. So, you can trust these services) and redirects back to your service. Now, your service is able to request an access token using a webservice provided by Drops and to request the users profile by another webservice that is also provided by Drops and requires a valid access token as parameter.
Your service has to be registered in Drops. For this purpose you have to send a mail to the Drops-Service administrator containing the following information:
client_id
: e.g. the name of your serviceredirectUri
: a URL of your service pointing to an action that
consumes the generated authorization code (e.g. if
https://example.com/oauth/code/<generated_code>
points to such an action,
the redirectUri
would be https://example.com/oauth/code/
)Info: Don't forget possible special signs at the end of the
redirectUri
(e.g.or
/
), because Drops simply concatinates the given URI and the generated code.
?code=
Additionally, it should be obvious that you have to know the URL of the Drops Service you want to connect to.
Implementing the handshake is very simple and consists of three steps:
(1) Implement an URL path pointing to an action of your service that redirects
(HTTP 303
or HTTP 302
) to:
<drops_url>/oauth2/code/get?client_id=<client_id>&response_type=code&state=<state>&redirect_uri=<redirect_uri>
The variables
, <drops_url>
and <client_id>
have been defined during preparation phase. <redirect_uri>
can be used to save the current state of the OAuth2
client across redirects. Additionally, you can add the query paramater state
indicating if the current
redirects was issued by an ajax request. In that case no login screen will be shown, but a JSON encoded error message is
returned.ajax
(2) The action handling the redirectUri
has to be implemented.
This action will be accessed by an HTTP redirect initiated by the Drops service.
It receives a code by a query parameter or inside the URL path and uses this code
to receive an OAuth 2 AccessToken
. For this purpose it calls the
Drops service directly using the webservice endpoint
and the following query parameter: <drops_url>/oauth2/access_token
grant_type=authorization_code
client_id=<client_id>
code=<received_code>
redirect_uri=<redirectUri>
(3) The responded AccessToken
can be used to request the users profile,
by requesting another webservice supplied by the Drops service:
<drops_url>/oauth2/rest/profile
access_token=<access_token>
An error-free response of the request for an access token will be a JSON:
{
"token_type" : "some_string",
"access_token" : "a random string",
"expires_in" : a long,
"refresh_token" : "a random string"
}
The key access_token
of such an response should be used as value of
the variable access_token
used in step 3.
An error-free response of the request for a profile will be a JSON describing a user as shown before.
Using this information your service is able to initiate a session for the user and your service.