Tutorial04 - Building a Custom Authenticator
Introduction
Datameer has built in user and group management that is used for authentication. However if you want to plug in your own authenticator into Datameer you can write a plug-in and create a class that implements datameer.dap.sdk.authentication.AuthenticatorExtension
. All such additional authenticator extensions are shown in the Admin tab > Authentication and an administrative user can select the authenticator that should be used.
Example Authenticator
You build a very simple authenticator that grants access to an analyst user john/doe. In addition user accounts for administration, users should be configurable on the authenticator set-up screen.
Here is the implementation for the TutorialAuthenticatorExtension:
package datameer.das.plugin.tutorial04; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import datameer.dap.sdk.authentication.AbstractAuthenticator; import datameer.dap.sdk.authentication.AuthenticatorExtension; import datameer.dap.sdk.common.GenericConfiguration; import datameer.dap.sdk.property.PropertyDefinition; import datameer.dap.sdk.property.PropertyGroupDefinition; import datameer.dap.sdk.property.PropertyType; import datameer.dap.sdk.property.WizardPageDefinition; public class TutorialAuthenticatorExtension extends AuthenticatorExtension { static final String PASSWORD = "password"; static final String NAME = "name"; /** * Returns the extension id. This must be unique for all extensions. * * @return the extension id. */ @Override public String getId() { return "TutorialAuthenticatorExtension"; } /** * Returns the name of this {@link AuthenticatorExtension}, which will be used in the UI when * setting up the authenticator. * * @return the name. */ @Override public String getName() { return "Tutorial Authenticator"; } /** * Adds property controls to the wizard page of the authenticator set-up screen so that users * can configure properties of this authenticator. * * @param wizardPageDefinition */ @Override public void populateAuthenticatorWizardPage(WizardPageDefinition wizardPageDefinition) { PropertyGroupDefinition propertyGroup = new PropertyGroupDefinition("Admin User", 1, 10); PropertyDefinition nameDefinition = new PropertyDefinition(NAME, "Admin user name", PropertyType.STRING); PropertyDefinition pwdDefinition = new PropertyDefinition(PASSWORD, "Admin user password", PropertyType.PASSWORD); propertyGroup.addPropertyDefinition(nameDefinition); propertyGroup.addPropertyDefinition(pwdDefinition); wizardPageDefinition.addPropertyGroup(propertyGroup); } /** * Creates an {@link AbstractAuthenticator} instance. * * @param conf * The configuration used to configure the instance. * @return an {@link AbstractAuthenticator} instance. */ @Override public AbstractAuthenticator createAuthenticator(GenericConfiguration conf) { String[] usernames = conf.getStringPropertyArray(NAME); String[] passwords = conf.getPasswordPropertyArray(PASSWORD); List<Account> exampleAccounts = new ArrayList<Account>(); for (int i = 0; i < passwords.length; i++) { exampleAccounts.add(new Account(usernames[i], passwords[i])); } return new TutorialAuthenticator(exampleAccounts, Arrays.asList(new Account("john", "doe"))); } }
In the populateAuthenticatorWizardPage(...)
method you define that up to 10 admin accounts can be configured on the authenticator set-up screen. Whatever has been set-up there persists in the Datameer database and gets passed into the createAuthenticator(...)
method. We grab the usernames and passwords and use this to create and configure an ExampleAuthenticator instance.
package datameer.das.plugin.tutorial04; import org.apache.commons.lang.builder.*; public class Account { private String _username; private String _password; public Account(String username, String password) { _username = username; _password = password; } public String getUsername() { return _username; } public String getPassword() { return _password; } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } @Override public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); } }
Implementing the authenticator itself is fairly simple. There are just a few methods that need to be implemented:
package datameer.das.plugin.tutorial04; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import com.google.common.collect.Lists; import datameer.dap.sdk.authentication.AbstractAuthenticator; import datameer.dap.sdk.authentication.GenericUser; import datameer.dap.sdk.authentication.Role; import datameer.dap.sdk.authentication.User; public class TutorialAuthenticator extends AbstractAuthenticator { private static final List<String> GROUPS = Arrays.asList("group"); private List<Account> _adminAccounts; private final List<Account> _analystAccounts; public TutorialAuthenticator(List<Account> adminAccounts, List<Account> analystAccounts) { _adminAccounts = adminAccounts; _analystAccounts = analystAccounts; } /** * Authenticates user by username / password. * * @param username * @param password * @return the authenticated user or null if the user could not be authenticated. */ @Override public User authenticateUser(String username, String password) { Account credentials = new Account(username, password); if (_adminAccounts.contains(credentials)) { return transformAccount(credentials, Role.ANALYST, Role.ADMIN); } if (_analystAccounts.contains(credentials)) { return transformAccount(credentials, Role.ANALYST); } return null; } /** * Lists all groups that are relevant to Datameer. * * @return all groups that are relevant to Datameer. */ @Override public Set<String> listGroups() { return new HashSet<String>(GROUPS); } /** * Lists all users that are relevant to Datameer. * * @return all users that are relevant to Datameer. */ @Override public List<User> listUsers() { List<User> users = Lists.newArrayList(); for (Account account : _adminAccounts) { users.add(transformAccount(account, Role.ANALYST, Role.ADMIN)); } for (Account account : _analystAccounts) { users.add(transformAccount(account, Role.ANALYST)); } return users; } @Override public void testConnection() { } User transformAccount(Account account, Role... roles) { return new GenericUser(account.getUsername(), account.getUsername() + "@datameer.com", GROUPS, Arrays.asList(roles)); } }
listGroups() and listUsers() could be long running operations and could return a really large number of groups and users. Datameer is aware of this and caches the results. Therefore these two methods are only called when the cache is getting refreshed. The cache is refreshed every minute, but this could be set-up in the conf/default.properties
file of Datameer.
Debugging
Add the following logging configurations into the file:Â <datameer-install-path>/conf/log4j-production.propertiesÂ
to view authentication attempts.
#file appender for external authentication log4j.category.org.springframework.security=DEBUG, auth # This will prevent the auth log messages from going into the conductor.log or console log4j.additivity.org.springframework.security=false log4j.appender.auth=org.apache.log4j.RollingFileAppender log4j.appender.auth.layout=org.apache.log4j.PatternLayout log4j.appender.auth.layout.ConversionPattern=%5p [%d{yyyy-MM-dd HH:mm:ss}] - %m%n log4j.appender.auth.File=logs/auth.log log4j.appender.auth.MaxFileSize=10000KB log4j.appender.auth.MaxBackupIndex=10 log4j.appender.auth.threshold=DEBUG
Source Code
This tutorial can by found in the Datameer plug-in SDK under plugin-tutorials/tutorial04
.