Introduction
The API supports OAUTH2 authentication from version 11.16 onwards. This feature allows a user to connect with an external account to obtain a JWT token to call the API services. The API is designed to support OpenID Connect, but can by used with some non OpenID servers.
Configuring a OAuth2 authentication server (provider)
configuration
To configurate an authenticiation server, go to REST API administration, in the OAuth2 Set-up section, then create a new configuration from scratch, or duplicate a provided preconfigured one.
The following default configurations are provided as standard:
google: to connect with Google Accounts, including G Suite (https://accounts.google.com/.well-known/openid-configuration)
microsoft: to connect with Microsoft Azure Active Directory (https://login.microsoftonline.com/common/.well-known/openid-configuration)
CautionIn the default configuration, the tenant is pre-positioned by the {tenant}
pattern that will have to be replaced by the appropriate tenant identifierlinkedin: (as a non openid example), to connect with LinkedIn
properties
Configuring an authentication server consists of:
set up a description
name of the configuration is the ID of the authentication server. user a simple alphanumeric string without accent or special characters
description, string
A short description
activated, boolean
The configuration of an authentication server must be enabled to be used to authenticate a user by this authentication server. If you try to use a disabled configuration, you will get a status 'Not found' (404).
set up the URLs that will be used for the authentication process
authorizationEndPoint, URL, mandatory, authorization end point
This end point is used in an authorization protocol by login form whose validation redirects to the appropriate sign in url to provide an access code to the server
tokenEndPoint, URL, mandatory, token end point
This end point converts the access code to a token
userInfoEndPoint, URL, mandatory, information end point
This end point is used to retrieve user information
tokenValidationEndPoint, URL, validation end point
This end point is used to validate a token.
introspectionEndPoint, URL, introspection end point (RFC 7662)
This end point is used to validate a token and retrieve information.
revocationEndPoint, URL, revocation end point
This end point is used to revoke the token after a JWT token is produced (this behavior can be disable even the end poitn URL is configured)
configurationEndPoint, URL, Configuration end point
This end point is not used at the moment, but will be used later to automatically configure an authentification server
jwkSetEndPoint, URL, JWK end point
This end point is used to get validation key for an internal validation (currently not implemented)
set up an API key
clientID, string, mandatory
The API client ID provided by the authentication server
clientSecret, string, mandatory
The API client secret provided by the authentication server
set up connection behavior
scopes, array of strings, scopes
The list of scopes the REST API can request to the authentication server. Usually, the scopes are the following opendid claims:
email, to get the email, usually used for pivot (common single property involving both users)
profile, to get name, locale…
openid, to get the open_id token
Without these scopes, it can be tricky to configure a completely discrete mapping, or to enable automatic user creation.
autorevoke, boolean, default is true if revocationEndPoint is set
activate revocation of token at the end of authentication process
prompt, string (as comma separated values)
a standard OAuth2 prompt mode, that must be supported by the authentication server
Example values are:
Google Account (see https://developers.google.com/identity/protocols/OpenIDConnect)
none
consent
select_account
Microsoft Azure AD (see https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-openid-connect-code)
none
login
consent
organization, object
the configuration of the token property that contains the organization ID (corresponding to the tenant for Microsoft Azure AD), if you want to check its value
propertyName, string, mandatory
the name of the property in the token
parameterName, string,
the name of the parameter, if the organization ID must be sent in URL
sendInAuthRequest, a boolean (default false).
If set to true, it means the organization ID must be add as parameter in URL
id, string
the value of the organization, to be checked
includeIdToken, boolean (default is false)
if true, the open_id token will be sent back in the sign in request.
loginhint, array of strings
The list of properties used the list of properties used to determine the login hints. The first one found is returned in the sign in response, and can be read invoking the whoami end point also. It can be used by a caller within the parameter login_hint.
Set up presentation,
Allows Web client application to automatically generates its login form (with data retrieved from the end point https://application/api/rest/auth/config)
providerName, string
The human readable name of the authentication server, used by example in the text of the "Sign in with <provider name>" button
icons, object
Different icons (FontAwesome) or images that can be used to decorate a "Connect with" button
usermappings
How to select an existing user, create a new one, or refuse connection, based on the token information.
Building client side login form
End point: https://application/api/rest/auth/config
This end point, which does not require any authorization, allows you to obtain a configuration that allows you to customize a client login form, and perform the URL invocations necessary for the connection workflow (authentication and authorizations) of your client application, in order to invoke the REST API.
Example:
Code Block |
---|
{
"providers":[
{
"id":"authlete",
"name":"Authlete",
"label":"Sign in with Authlete",
"loginHref":"https://application/api/rest/auth/login/authlete",
"signinHref":"https://application/api/rest/signin/authlete",
"icons":{
"main":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/authlete/main",
"backgroundcolor":"black"
}
}
},
{
"id":"facebook",
"name":"Facebook",
"label":"Sign in with Facebook",
"loginHref":"https://application/api/rest/auth/login/facebook",
"signinHref":"https://application/api/rest/signin/facebook",
"icons":{
"main":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/facebook/main",
"backgroundcolor":null
},
"fa":{
"type":"fontawesome",
"name":"fa-facebook",
"color":"white",
"backgroundcolor":"#3B5998"
}
}
},
{
"id":"google",
"name":"Google",
"label":"Sign in with Google",
"loginHref":"https://application/api/rest/auth/login/google",
"signinHref":"https://application/api/rest/signin/google",
"icons":{
"main":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/google/main",
"backgroundcolor":null
},
"bitmap_light":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/google/bitmap_light",
"backgroundcolor":null
},
"bitmap_dark":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/google/bitmap_dark",
"backgroundcolor":null
},
"fa":{
"type":"fontawesome",
"name":"fa-google",
"color":"#DB4437",
"backgroundcolor":null
}
}
},
{
"id":"microsoft",
"name":"Microsoft",
"label":"Sign in with Microsoft",
"loginHref":"https://application/api/rest/auth/login/microsoft",
"signinHref":"https://application/api/rest/signin/microsoft",
"icons":{
"main":{
"type":"fontawesome",
"name":"fa-microsoft",
"color":"#00a1f1",
"backgroundcolor":null
},
"svg":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/microsoft/svg",
"backgroundcolor":null
},
"bitmap":{
"type":"url",
"url":"https://application/api/rest/auth/rsc/microsoft/bitmap",
"backgroundcolor":null
}
}
}
],
"version":"1.0",
"status":200,
"time":14
} |
The array providers lists every available oauth2 authentication server. For each of them, you’ll get:
id: an unique ID
name: a name
label a standard label for a button
loginHref: the URL of the login end point
signinHref: the URL of the signin end point
icons: a list of icon descriptions
Each icon has an identifier from the configuration.
TipThe configuration does not impose a nomenclature, but to manage automation more easily, recommendations are given (see configuration). type: the type of icon
Types:
url: the image can be displayed via an <IMG> tag, from the provided URL
The specific parameters are:
url: the url of the image
fontawesome: then image is an Font Awesome icon.
The specific parameters are:
name: the Font Awesome name
color: (optional, could be null) a value for the CSS Color property.
backgroundcolor: (optional, could be null) a value for the CSS Background-Color property.
ImportantNot all icons may be viewable depending on the version of Font Awesome used on the client. It is possible to test the existence of an icon with the following JavaScript function (checkFontAwesome("fa-totest") returns true if icon exists, false else).
Example for FontAwesome 4.7:
Code Block |
---|
function checkFontAwesome(name) {
return faUnicode(name)!=null;
}
function faUnicode(name) {
var testI = document.createElement('i');
var char;
testI.className = 'fa ' + name;
document.body.appendChild(testI);
char = window.getComputedStyle( testI, ':before' )
.content.replace(/'|"/g, '');
testI.remove();
if ( char==='none' ) {
return null;
}
return char.charCodeAt(0);
} |
token introspection or decoding
Authentication end point
End Point : https://application/api/rest/signin/{providerId}
This end point provides valid connection data (JWT tokens) for using the REST API.
There are many and varied ways of using it :
by passing token (produced by the authentication server), as parameters access_token, token_type and expires_in (including multipart/form-data).
via the Authorization header as a Bearer,
in the content as a response to the invocation of the corresponding end point of the authentication server, i. e. as:
Code Block { "access_token": "the access token...", "token_type": "Bearer", "expires_in": number of seconds before expiration }
by passing a code (produced by the authentication server) as parameter code. This is the mode used while redirection from the authentication server login page.
When a code is provided, it is first transformed into a token. The token is validated and the user data are extracted. If code and token are provided, it is also checked that they are consistent with each other.
Client side login workflow handled by server
End Point : https://application/api/rest/auth/login/{providerId}
This endPoint displays the HTML login form associated with the authentication server. When the user is authenticated, the form redirects to the end authentication point of the REST API (https://application/api/rest/signin/{providerId}).
It is possible at the end to get back the REST API JWT tokens:
as request response (not recommended, particularly within Web MPA)
as request parameter
as session attribute
as URL fragment
request response
As the answer will be displayed directly in the browser, it is not easy to follow the standard navigation of the site. In addition, cross-domain protection prevents window events from being listened to. The only solution is to polling the login window until the location and content-type are readable, then close this window and pass the contents of the window back to the main window to continue on to the authenticated navigation on the site.
This method is not recommended because: * it requires to open a second window for the login specific to the authentication provider * this window remains visible for a short time with the entire response visible (due to the polling period and the reaction time of the navigator) * on some browsers, the answer will not be displayed in the window and will cause the workflow type of file downloading - the implementation in JavaScript is not very elegant
request parameter
When invoking the login end point, add the following parameters :
redirectUrl: String. The URL of your login page (the one where your "Sign in with…" buttons are displayed). At the end of the authentication process, it will be invoked by passing the connection data (refresh and access JWT tokens and other related data - see JWT token authentication for more details) the authresponse parameter.
redirectUrlMode: with the value parameter
It is easy to retrieve the parameter in JSP or JavaScript. This method is not recommended if the web application is internal to the Wedia system because the setting is visible in the address bar. It is possible to remove it in JavaScript, but this requires reloading the page. Be careful not to use direction window.document.location.href as a value for the parameter redirectUrl, if you have not deleted the parameter: indeed, the url will at some point be passed in the Referer header and it happens that the server servlet causes an error because of its excessive size.
session attribute
When invoking the login end point, add the following parameters :
redirectUrl: String.
The URL of your login page (the one where your "Sign in with…" buttons are displayed). At the end of the authentication process, it will be invoked by passing the connection data (refresh and access JWT tokens and other related data - see JWT token authentication for more details) the authresponse parameter.
redirectUrlMode: (optional, default value is attribute) with the value attribute
This mode passes connection data through a session attribute that can be easily retrieved in JSP (normally, the one that generates the login page of your application) and without this data being visible in the URL. The defect of this method is that it can only be used in an application hosted by the Wedia server.
ImportantIt is also important for the processing JSP to delete the attribute after retrieving it to avoid unintended subsequent reuse.
URL fragment
This is a variation a the request parameter, but as the value is in the fragment, you can get easily in your Web client application in JavaSCript, but this data will never be send to server if the URL is invoked.
Client side login handled by application
End Point : https://application/api/rest/signin/{providerId}
Just as the authentication server will provide a code or token to the end authentication point of the REST API by redirection, it is possible to invoke the URL directly from a client.
The authorization modes that pass through the server only support the response type code, but a client implementation can use the mode it wants (code, token, code token, credentials (not recommended)…) as long as it respects at the end one of the processing modes implemented by this end point.
The state parameter
The state parameter can be passed to login URI end point (https://application/api/rest/auth/login/{providerId}) to verify the token when it is obtained. It’s a random string your application will generate and the final response will return you as it stands, allowing you to check if the response matches the request.
Dealing with errors
Use the same methods to handle errors as any other end point of the REST API. Sometimes, the error can go directly from the authentication server: in this case, it is mapped to an internal error but its documentation can be external.
User mapping
The configuration of each OAuth2 provider allows to configure how a Wedia user (internal user) is mapped with the external user.
There are 2 phases: selection and mapping.
selection
The configuration of the selection phase allows to indicate how a mapping will be selected or not according to the user’s information.
You can configure directly in the configuration form which properties or parts of properties should be used and which values they should have to select which mapping. Or you can do this with a component coded in Java (loaded from your own plug-in).
If no mapping is selected, the user is denied access to the API
mapping
Mapping consists in associating an internal user with the user who has authenticated himself (external user), using properties of the internal user and the external user to bind them (same email, same name, etc.). Mappings can be configured directly in the configuration form or done via a component coded in Java (loaded from your own plug-in).
With the mapping, in the configuration form, you can:
bind an existing internal user corresponding to the external user
create a new internal user (or deny access to user that hasn’t any internal user corresponding)
bind to a dedicated reserved user (limited rights for example). This mode does not prevent the connection from associating the external user’s personal information (e. g. e-mail or name) with the connection.
trigger post connection
It is possible to set up a component coded in Java (loaded from your own plug-in) that will be invoked when the connection is effective.
Support of protocol and limitations
Java coded extensions
To code Java extensions, you only to get from WXM-RESTAPI plug-in the jar restapibs.jar and put in your own plug-in. Then implement the interface you need and build the class into a jar you’ll include to the plug-in. Then activate the plug-in. That’s all for usual cases: some times, you’ll have to select the plugin and the class in a configuration form.
Available interfaces:
com.noheto.restapi.OAuth2UserBinding: this interface allows you to code the selection of mapping, the mapping or both processes in Java. The data of user and provider can be get from the invocation context with the interface com.noheto.restapi.OAuth2UserBindingContext.
It is better to use the abstract base provided than to fully implement the interface (com.noheto.restapi.OAuth2UserBindingAdapter). You will only have to implement the parts that interest you without thinking about the rest..
com.noheto.restapi.OAuth2IdTokenValidation: this interface allows you to validate a token by Java code. This is useful if you the provider provides a Java API to validate token. The data of user and provider can be get from the invocation context with the interface com.noheto.restapi.OAuth2IdTokenValidationContext.
It is better to use the abstract base provided than to fully implement the interface (com.noheto.restapi.OAuth2IdTokenValidationAdapter). You will only have to implement the parts that interest you without thinking about the rest.
mapping
Example of selection and mapping Java-coded extension
This is the one use for google authentication in ClubWed demo.
Code Block |
---|
package fr.wedia.clubwed.restapi;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
import org.apache.commons.lang3.StringUtils;
import com.noheto.restapi.Email;
import com.noheto.restapi.OAuth2UserBindingAdapter;
import com.noheto.restapi.OAuth2UserBindingContext;
import com.noheto.restapi.UserBinding;
import com.noheto.restapi.UserBindingAdapter;
import fr.wedia.clubwed.restapi.config.UserConfig;
import fr.wedia.clubwed.restapi.plugin.PluginManager;
import wsnoheto.engine.BatchObjectsIterable;
import wsnoheto.engine.CTObject;
import wsnoheto.engine.CTObjects;
import wsnoheto.engine.FieldNotFoundException;
import wsnoheto.engine.PreparedWhere;
import wsnoheto.engine.PreparedWhereException;
public class OAuth2UserMapper extends OAuth2UserBindingAdapter {
@Override
public boolean isSelected(OAuth2UserBindingContext context) {
// all user with a email will match this adapter
String email = context.getProperty("email");
return StringUtils.isNotBlank(email);
}
private void logDebug(OAuth2UserBindingContext context, Supplier<String> messageSupplier) {
if ( context.getLogger().isDebugEnabled() ) {
context.getLogger().debug( messageSupplier.get() );
}
}
@Override
public UserBinding getBinding(OAuth2UserBindingContext context) {
try {
logDebug(context, ()-> "OAuth2UserMapper: get user binding... " + context);
String organizationProperty = context.getOrganizationProperty();
logDebug(context, ()->"OAuth2UserMapper: organizationProperty " + organizationProperty);
if ( StringUtils.isBlank(organizationProperty) ) {
// if user isn't part of a Google Suite, map to a default user
return mapDefaultUser(context);
}
else {
String organizationPropertyValue = context.getProperty(organizationProperty);
logDebug(context, ()->"OAuth2UserMapper: organizationProperty value " + organizationPropertyValue);
if ( "wedia.fr".equals(context.getProperty(organizationPropertyValue)) ) {
// if user is part of the Wedia Google Suite, map to a wedia user
return mapWediaUser(context);
}
else {
// else, map to a default user
return mapDefaultUser(context);
}
}
}
catch (Throwable t) {
context.getLogger().error("OAuth2UserMapper: cannot map user " + context, t);
// in case of error, fallback to the following component, if any
return UserBindingAdapter.IGNORE;
}
}
// map a user for a Wedia user account
private UserBinding mapWediaUser(OAuth2UserBindingContext context) throws Throwable {
// check the email address
String email = context.getProperty("email");
if ( StringUtils.isBlank(email) ) {
// blank email address is rejected (unauthorized status)
context.getLogger().warn("OAuth2UserMapper: no email, cannot map user " + context );
return UserBindingAdapter.REJECT;
}
// use the helper to decode the email address
Email emailHandler = new Email(email);
// get the email local part (part before @)
String localPart = emailHandler.getLocalPart();
logDebug(context, ()-> "OAuth2UserMapper: look for wedia user with email " + localPart + "@..." );
// look for the corresponding wedia user (based on email address local part)
CTObject user = lookForWediaUser(context, localPart);
if ( user!=null ) {
// if user found, then update some properties and return the corresponding connection status
logDebug(context, ()-> "OAuth2UserMapper: user found " + user + " " + context);
return new UserBindingAdapter(updateWediaUser(context, user));
}
logDebug(context, ()-> "OAuth2UserMapper: user not found " + user + " " + context);
// if no corresponding user found, then create some new one and return the corresponding connection status, with the email domain forced to @wedia.fr
return createUser(context, localPart, localPart + "@wedia.fr");
}
// map a user for any user account
private UserBinding mapDefaultUser(OAuth2UserBindingContext context) throws Throwable {
// check the email address
String email = context.getProperty("email");
if ( StringUtils.isBlank(email) ) {
// blank email address is rejected (unauthorized status)
context.getLogger().warn("OAuth2UserMapper: no email, cannot map user " + context );
return UserBindingAdapter.REJECT;
}
logDebug(context, ()-> "OAuth2UserMapper: look for user with email " + email );
// look for the corresponding wedia user (based on email)
CTObject user = lookForUser(context, email);
if ( user!=null ) {
// if user found, then update some properties and return the corresponding connection status
= logDebug(context, ()-> "OAuth2UserMapper: user found " + user + " " + context);
return new UserBindingAdapter(updateUser(context, user));
}
logDebug(context, ()-> "OAuth2UserMapper: user not found " + user + " " + context);
// if no corresponding user found, then create some new one and return the corresponding connection status, with the user email address
return createUser(context, email, email);
}
private CTObject lookForWediaUser(OAuth2UserBindingContext context, String localPart) throws PreparedWhereException {
// as some users has been created with @wedia.fr and others width @wedia-group.com, look for the one which have the same email local part.
String wediaFrEmail = localPart + "@wedia.fr";
String wediaGroupEmail = localPart + "@wedia-group.com";
PreparedWhere where = PreparedWhere.load()
.or(PreparedWhere.load("?").addStringEquals("email", wediaFrEmail))
.or(PreparedWhere.load("?").addStringEquals("email", wediaGroupEmail));
return lookForUser(context, where);
}
private CTObject lookForUser(OAuth2UserBindingContext context, String email) throws PreparedWhereException {
// look for the one which have the same email.
PreparedWhere where = PreparedWhere.load("?").addStringEquals("email", email);
return lookForUser(context, where);
}
private CTObject lookForUser(OAuth2UserBindingContext context, PreparedWhere where) {
logDebug(context, ()-> "look for user: " + where);
// neglects any duplicates (takes the first one found)
return StreamSupport.stream(BatchObjectsIterable.all("user").where(where).max(1).spliterator(), false).findAny().orElse(null);
}
private UserBinding createUser(OAuth2UserBindingContext context, String login, String email) throws Throwable {
// created a new user
CTObject object = CTObjects.create("user");
UserConfig config = PluginManager.getInstance().getUserConfig();
// get a default user configuraiton from plug-in configuration, if any
if ( config!=null && !config.isEmpty() ) {
config.setValues(object, context);
}
else {
object.setProperty("activated", "1"); // enabled user by default
object.setProperty("role", "32"); // default 32
object.setProperty(7, "6"); // default status (published)
object.setProperty("login",login); // set the login name
object.setProperty("type", "1"); // set the type (user or group)
object.setProperty("groups", ",1,"); // set a brand
object.setProperty("parentgroup", "74"); // set the parent owner rog
setImage(object, context); // download and set avatar picture
setUserProperties(context, object); // sets specific user properties
}
object.setProperty("email",email);
String userId = object.JSPSave(PluginManager.getPluginUser(), true, true);
context.getLogger().info("OAuth2UserMapper: user created with id = " + userId + " " + context);
return new UserBindingAdapter(CTObjects.loadObjectById("user", userId)); // create a connected status
}
private void setImage(CTObject object, OAuth2UserBindingContext context) {
String imageurl = context.getProperty("picture");
if ( !StringUtils.isBlank(imageurl) ) {
try {
object.setProperty("avatar", new URL(imageurl));
} catch (FieldNotFoundException | IOException e) {
context.getLogger().error("OAuth2UserMapper: connot import avatar " + context, e);
}
}
}
// the upstream procedure distinguishes Wedia users from others, but the concrete implementation does the same
private CTObject updateWediaUser(OAuth2UserBindingContext context, CTObject object) {
in both cases.
return updateUser(context, object);
}
// update user properties (some in the openid token propertie)
private CTObject updateUser(OAuth2UserBindingContext context, CTObject object) {
setUserProperties(context, object);
try {
String userId = object.JSPSave(PluginManager.getPluginUser(), true, true);
context.getLogger().info("OAuth2UserMapper: user with id = " + userId + " updated " + context);
return CTObjects.loadObjectById("user", userId);
}
catch (Throwable e) {
context.getLogger().error("OAuth2UserMapper: cannot update user with id = " + object.getId() + " " + context);
return object;
}
}
// updates some specifiq propterties of the user
private void setUserProperties(OAuth2UserBindingContext context, CTObject object) {
UserConfig config = PluginManager.getInstance().getUserConfig();
if ( config!=null && !config.isEmpty() ) {
config.updateValues(object, context);
}
else {
object.setProperty("name", context.getProperty("name")); // reset the name () as it coud have changed
object.setProperty("lastname", context.getProperty("family_name")); // reset the name as it coud have chan
String locale = context.getProperty("locale");
if ( !StringUtils.isBlank(locale) ) {
String langId = getLanguageId(locale);
if ( langId!=null ) {
object.setProperty("bolang", langId);
}
}
try {
// stores the avatar if there is not any image in the property
File file = object.getPropertyAsFile("avatar");
if ( object.getPropertyAsFile("avatar")==null || !file.isFile() ) {
setImage(object, context);
}
} catch (Throwable e) {
}
}
}
// look for the language id for specidied locale, looking first the generic language, then the specific (language + country)
private String getLanguageId(String locale) {
PreparedWhere where = PreparedWhere.load("?").addStringEquals("code", locale);
CTObject lang = StreamSupport.stream(BatchObjectsIterable.all("lang").where(where).max(1).spliterator(), false).findAny().orElse(null);
if (lang==null ) {
String[] splited = locale.split("_|-");
if ( splited.length==2 ) {
return getLanguageId(splited[0]);
}
}
return lang==null?null:lang.getId();
}
} |
Example of trigger post connection
Send a mail with data from a user:
Code Block |
---|
package fr.wedia.clubwed.restapi;
import java.io.IOException;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import com.noheto.restapi.OAuth2UserBindingAdapter;
import com.noheto.restapi.OAuth2UserBindingContext;
import fr.wedia.clubwed.restapi.config.PropertyConfig;
import fr.wedia.clubwed.restapi.config.ServerConfig;
import fr.wedia.clubwed.restapi.model.Message;
import fr.wedia.clubwed.restapi.model.Property;
import fr.wedia.clubwed.restapi.plugin.PluginManager;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import wsnoheto.engine.IObjectReadOnly;
public class Mailer extends OAuth2UserBindingAdapter {
@Override
public void postConnect(OAuth2UserBindingContext context, IObjectReadOnly user, String username, String useremail) {
LocalDateTime date = LocalDateTime.now(); // current data
if ( PluginManager.getInstance()==null ) return;
ServerConfig config = PluginManager.getInstance().getConfig(context.getServerId()); // provider configuration to generate mail
if ( config!=null ) {
// create data model for freemarker template
Message message = new Message();
message.setDate(date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))); // date
message.setTime(date.format(DateTimeFormatter.ofPattern("HH:mm"))); // time
message.setService(config.toService()); // provider
// read mail properties from user (the ones configured for the provider)
for(PropertyConfig propertyDef : config.getProperties()) {
Property property = propertyDef.toProperty(context);
if ( property!=null ) {
message.appendProperty(property);
}
}
// some additionnals properties
for(PropertyConfig propertyDef : config.getAdditionals()) {
Property property = propertyDef.toProperty(context);
if ( property!=null ) {
message.appendAdditional(property);
}
}
// generate the email and send it
try {
Template template = PluginManager.getInstance().getTemplate("mail.ftlh");
try(StringWriter stringWriter = new StringWriter()) {
template.process(message, stringWriter);
stringWriter.flush();
sendMail(stringWriter.toString());
}
} catch (IOException e) {
PluginManager.getInstance().error("Cannot send mail",e);
} catch (TemplateException e) {
PluginManager.getInstance().error("Cannot send mail",e);
}
}
}
private void sendMail(String body) {
PluginManager manager = PluginManager.getInstance();
try {
wsnoheto.mail.AdminSmtp asmtp = new wsnoheto.mail.AdminSmtp();
asmtp.setTo(manager.getEmailAddress());
asmtp.setFrom(manager.getEmailReply());
asmtp.setSubject(manager.getEmailObject());
asmtp.setBody(body);
asmtp.setModeHtml(true);
asmtp.send();
} catch (Throwable e) {
manager.error("cannot send mail", e);
}
}
} |