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:
- Email-based user sign up flow.
- Email-based password reset flow.
- Authentication using credentials (email + password).
- Account linking: link a social provider profile to your credentials profile.
- OAuth1 Twitter authentication (not turned on by default).
- Anonymous or authenticated access to home page.
- Profile information for authenticated users.
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:
- Supporters can have a Crew
- Supporters can have a selection of the four Viva con Agua pillars (
operation
- dt. "Aktionen",finance
,education
andnetwork
)
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
orv
: 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 version1.0.0
. Additionally, this version supports theall
filter: Returns all requested entities and ignores a given pagination filter. So, ifall
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, the lastId
inside the page
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 given page
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 header Content-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
points to such an action, thehttps://example.com/oauth/code/<generated_code>
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:
- endpoint:
<drops_url>/oauth2/rest/profile
- query string:
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.
- [B] #340 - Bug: Nats heap space error
- [I] #322 - Users address
- [B] #339 - Bug: PasswordInfoDao
- [I] #329 - Remove MongoDB
- [I] #330 - Remove unused views
- [B] #333 - Delete failed
- [I] #324 - Export users crew to Pool 1
- [B] #319 - Case sensitivity not working for import
- [I] #318 - Email for login should not be case sensitive
- [B] #317 - Failure on UUID requests for Pool1 API
- [B] #315 - Failure on Pool1-API Response interpretation
- [B] #313 - Failure on Pool1 configuration
- [B] #312 - Pool 1 Service is called in controller and UserService
- [F] #259 - New email
- [F] #254 - WebApp action for user delete
- [F] #6 - Delete their own account
- [B] #310 - SignUp does not allow optional values
- [F] #306 - Update Pool1 API calls
- [B] #305 - Stolen Access Right
- [F] #303 - Role selection
- [I] #301 - Add route to receive user from WebApp
- [B] #299 - Cities are not deleted on crew update
- [I] #298 - WebApp-REST interface for crew selection
- [B] #297 - Crews view is outdated
- [F] #82 - Extend profile image CRUD
- [B] #294 - Sign Up not possible
- [B] #292 - Error on reset password
- [B] #291 - Error on calling users widget
- [I] #290 - Non-JSON endpoint sign up mail link
- [B] #289 - Asynchronous insert and find
- [B] #288 - Error on supporter roles (SignUp)
- [B] #287 - Status codes on Error
- [F] #284 - Extended Role-based Access Control
- [W] #226 - Group of users
- [W] #225 - Small inline user
- [W] #224 - Autocomplete Widget
- [F] #264 - CRUD Crews WebApp Controller
- [I] #245 - Frontend Controller SignUp
- [I] #244 - FrontendController SignIn
- [I] #241 - OAuth2 redirect to HTML during Ajax request
- [F] #227 - Impressum: Action for handling Impressum
- [I] #221 - Pool1 OES trouble
- [F] #93 - Add generic filter for Crews and Users
- [F] #32 - Organizations are configurable
- [I] #222 - Allow simple filter OES
- [B] #215 - OAuth 2 Provider does not implements query params
- [B] #217 - Missing template on BadRequest
- [B] #213 - OAuth Database bug
- [F] #208 - Add MariadbPool1UserDao
- [F] #91 - handler for dispenser templates
- [I] #123 - Split Task and AccessRight Model in Database and Business Models
- [I] #114 - Mongo to MariaDB
- [B] #205 - Redirect Problems in a running Session
- [F] #202 - OES Create Update Delete
- [F] #107 - User import from external tools
- [F] #38 - logout event
- [B] #186 - the oauth handshake contains the client_secret
- [I] #183 - Accessibility of views bases on Pool 1 connection
- [F] #180 - Allow webservice secrets
- [F] #106 - Send new registered user to Pool 1
- [I] #124 - Use singular labels for rest urls
- [I] #120 - False Translations
- [I] #117 - Translation
- [F] #102 - CRUD for Webservice
- [I] #111 - Default redirect page after login
- [F] #73 - RESTful interface to request access rights
- [B] #98 - Comments run into error on publish
- [I] #69 - Associate Access Rights to Microservices
- [F] #71 - Relations between users and tasks
- [F] #70 - Relations between Tasks and Access Rights
- [F] #47 - Task Object
- [B] #85 - Preselection of crew does not work
- [F] #12 - Extra profile image
- [F] #5 - Users profile
- [F] #35 - Remove active flag
- [F] #22 - ReDesign
- [F] #55 - Add relational database support for task managment
- [B] #61 - Clients are not authorized to get an Access Token
- [I] #60 - Remove uneeded attribute OAuthClient
- [I] #59 - Remove OAuthClient Secret
- [I] #26 - Crews WS: IDs
- [I] #24 - WS: Support requesting all crews
- [I] #25 - Versioning for webservice
- [I] #18 - Multiple search conditions
- [F] #16 - Test Data
- [F] #9 - Supporter Webservice
- [F] #8 - Different groups
- [F] #10 - Configurable geography
- [F] #7 - Different roles
- [F] #4 - Oauth 2 provider
- [F] #3 - Users login
- [F] #2 - Users are able to register themselves
- [F] #1 - Represent a user