Authenticator: Difference between revisions

From Obsidian Scheduler
Jump to navigationJump to search
No edit summary
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Out-of-the-box Obsidian includes 2 authentication providers. Native authentication, which is managed in the database, and LDAP-supported authentication. But you also have the option to implement your own authenticator or even customize our LDAP authenticator. Below you'll find directions on how to do both. If after working through the instructions you are having problems or otherwise have some questions, feel free to reach out to us either through our Free Live Chat Support link to the left, or by [[Contact_Carfey_Software|contacting us]]. Before you get started, we recommend you are familiar with the [[Admin_User_Management#User_Rights|Roles]] that Obsidian defines to allow assignment of access restrictions.
Obsidian includes two authentication providers out-of-the-box: native authentication, which is managed in the database, and LDAP-supported authentication. But you also have the option to implement your own authenticator or even customize our LDAP authenticator. Below you'll find directions on how to do both. If you are having problems or have some questions, feel free to reach out to us either through our Free Live Chat Support link to the left, or by [[Contact_Carfey_Software|contacting us]]. Before you get started, we recommend you are familiar with the [[Admin_User_Management#User_Rights|Roles]] that Obsidian uses for access control.


== Developing an Authenticator ==
== Developing an Authenticator ==
Obsidian uses any valid implementation of the <code>com.carfey.suite.security.Authenticator</code> interface.  
Obsidian uses any valid implementation of the <code>com.carfey.suite.security.Authenticator</code> or <code>com.carfey.suite.security.remember.Authenticator</code> interface.  


=== Implementation of a Custom Authenticator ===
=== Implementation of a Custom Authenticator ===
<code>com.carfey.suite.security.User authenticate(String username, String pass) throws com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>
'''<code>com.carfey.suite.security.User authenticate(String username, String pass) throws com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>'''
 
As of Obsidian 6.1.1, you may optionally implement REST authentication distinctly. Defaults to standard authentication when not implemented.
'''<code>com.carfey.suite.security.User authenticateREST(String username, String pass) throws com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>'''


This method authenticates and returns a user with roles defined. Given a user name and a password either return a valid <code>com.carfey.suite.security.User</code> object or throw a <code>com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>.
This method authenticates and returns a user with roles defined. Given a user name and a password either return a valid <code>com.carfey.suite.security.User</code> object or throw a <code>com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>.
Line 11: Line 14:


If authentication is successful, the <code>com.carfey.suite.security.User</code> returned must have all its role memberships defined. This is done using the <code>com.carfey.suite.security.Role</code> class. The assignment of <code>Role</code>s to a <code>User</code> can be done using any of the public constructors/setters or logical combination thereof defined below.
If authentication is successful, the <code>com.carfey.suite.security.User</code> returned must have all its role memberships defined. This is done using the <code>com.carfey.suite.security.Role</code> class. The assignment of <code>Role</code>s to a <code>User</code> can be done using any of the public constructors/setters or logical combination thereof defined below.
'''<code>public boolean supportsRememberMe()</code>'''
from <code>com.carfey.suite.security.remember.Authenticator</code> available as of Obsidian 3.5.0.
This method, from <code>com.carfey.suite.security.remember.Authenticator</code>, indicates whether the Authenticator supports Obsidian's remember me feature available in the Web Admin login. This is primarily used to ensure the remember me feature is not exposed in environments where the user could be disabled, made inactive or otherwise invalidated while allowing a lookup to succeed - as may be the case in directory authentication implementations such as LDAP.
=== Users ===


<pre>
<pre>
Line 26: Line 37:


=== Roles ===
=== Roles ===
There are convenience constants that you should use in defining your role memberships. They can be found at <code>com.carfey.ops.Constant</code>. The constants are <code>ADMIN_ROLE</code>, <code>WRITE_ROLE</code>, <code>LIMITED_READ_ROLE</code> and <code>API_ROLE</code>. ''Default'' rights are assumed for any authenticated user. Therefore, if someone authenticates that should not have access, throw a <code>com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>.
There are convenience constants that you should use in defining your role memberships. They can be found at <code>com.carfey.ops.Constant</code>. The constants are <code>ADMIN_ROLE</code>, <code>WRITE_ROLE</code>, <code>LIMITED_READ_ROLE</code> and <code>API_ROLE</code>. As of ''5.0.0'', you can also use <code>OPERATOR_ROLE</code> and <code>AUTHOR_ROLE</code>. ''Default'' rights are assumed for any authenticated user. Therefore, if someone authenticates that should not have access, throw a <code>com.carfey.suite.security.Authenticator.AuthenticationFailedException</code>.


When assigning the user's <code>com.carfey.suite.security.Role</code>s, use the constructor <code>public Role(String roleId, String roleName)</code> using the appropriate constant for both the <code>roleId</code> and <code>roleName</code>.
When assigning the user's <code>com.carfey.suite.security.Role</code>s, use the constructor <code>public Role(String roleId, String roleName)</code> using the appropriate constant for both the <code>roleId</code> and <code>roleName</code>. Role meanings are defined in [[Admin_User_Management#User_Rights | User Rights]].


=== Putting it All Together ===
=== Putting it All Together ===
Line 112: Line 123:
}
}
</pre>
</pre>
The new hire logs in using her username ''newHire'' and her password is ''i am a new hire''. You determine that her password is valid and matches with the user and she has ''OPERATOR_ROLE'' rights only for the job folder roots ''DevOps'' and ''FinOps''.
private static final String DEV_OPS_OPERATOR = "DevOps-" + OPERATOR_ROLE;
private static final String FIN_OPS_OPERATOR = "FinOps-" + OPERATOR_ROLE;
public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "newHire"
    //pass is "i am a new hire"
    //credentials are valid, the new hire has OPERATOR_ROLE access for job folder roots ''DevOps'' and ''FinOps''
    User user = new User(username);
    user.setRoles(Arrays.asList(new Role(DEV_OPS_OPERATOR, DEV_OPS_OPERATOR), new Role(FIN_OPS_OPERATOR, FIN_OPS_OPERATOR)));
    return user;
}


== Customizing our LDAP Authenticator ==
== Customizing our LDAP Authenticator ==
Line 133: Line 158:
====<code>protected boolean isMemberOfGroup(DirContext authContext, String groupName, String dn) throws NamingException</code>====
====<code>protected boolean isMemberOfGroup(DirContext authContext, String groupName, String dn) throws NamingException</code>====


Checks the authenticated <code>DirContext</code> for group membership. Used to determine ''Default'' rights to Obsidian, in addition to its defined rights [[Admin_User_Management#User_Rights|roles]]. Currently queries <code>"uniquemember"</code>,<code>"uniqueMember"</code>,<code>"member"</code>,<code>"roleOccupant"</code> and <code>"memberOf"</code> attributes for any match on the ''dn''.
Checks the authenticated <code>DirContext</code> for group membership. Used to determine ''Default'' rights to Obsidian, in addition to its defined rights [[Admin_User_Management#User_Rights|roles]]. Currently queries <code>"uniquemember"</code>,<code>"uniqueMember"</code>,<code>"member"</code>,<code>"roleOccupant"</code>, <code>"memberOf"</code> and <code>"MemberOf"</code> attributes for any match on the ''dn''.
 
== Deploying your Authenticator ==
Once your authenticator class is written, you need to deploy the compiled class and any dependent libraries to your admin web application instances as JAR files. These should be placed under the <code>/WEB-INF/lib</code> directory of either the Obsidian web application directory or WAR file.

Latest revision as of 18:15, 12 March 2025

Obsidian includes two authentication providers out-of-the-box: native authentication, which is managed in the database, and LDAP-supported authentication. But you also have the option to implement your own authenticator or even customize our LDAP authenticator. Below you'll find directions on how to do both. If you are having problems or have some questions, feel free to reach out to us either through our Free Live Chat Support link to the left, or by contacting us. Before you get started, we recommend you are familiar with the Roles that Obsidian uses for access control.

Developing an Authenticator

Obsidian uses any valid implementation of the com.carfey.suite.security.Authenticator or com.carfey.suite.security.remember.Authenticator interface.

Implementation of a Custom Authenticator

com.carfey.suite.security.User authenticate(String username, String pass) throws com.carfey.suite.security.Authenticator.AuthenticationFailedException

As of Obsidian 6.1.1, you may optionally implement REST authentication distinctly. Defaults to standard authentication when not implemented. com.carfey.suite.security.User authenticateREST(String username, String pass) throws com.carfey.suite.security.Authenticator.AuthenticationFailedException

This method authenticates and returns a user with roles defined. Given a user name and a password either return a valid com.carfey.suite.security.User object or throw a com.carfey.suite.security.Authenticator.AuthenticationFailedException.


If authentication is successful, the com.carfey.suite.security.User returned must have all its role memberships defined. This is done using the com.carfey.suite.security.Role class. The assignment of Roles to a User can be done using any of the public constructors/setters or logical combination thereof defined below.

public boolean supportsRememberMe()

from com.carfey.suite.security.remember.Authenticator available as of Obsidian 3.5.0.

This method, from com.carfey.suite.security.remember.Authenticator, indicates whether the Authenticator supports Obsidian's remember me feature available in the Web Admin login. This is primarily used to ensure the remember me feature is not exposed in environments where the user could be disabled, made inactive or otherwise invalidated while allowing a lookup to succeed - as may be the case in directory authentication implementations such as LDAP.

Users

public User(String userId)

public User(String userId, Set<Role> roles, String firstName, String lastName, String email, boolean active*)

public User(String userId, List<Role> roles, String firstName, String lastName, String email, boolean active*)

public void setRoles(Set<Role> roles)

public void setRoles(List<Role> roles)

* Note: If you wish to implement active user enabling/disabling, you must do so in your Authenticator throwing com.carfey.suite.security.Authenticator.AuthenticationFailedException when inactive users attempt to login.

Roles

There are convenience constants that you should use in defining your role memberships. They can be found at com.carfey.ops.Constant. The constants are ADMIN_ROLE, WRITE_ROLE, LIMITED_READ_ROLE and API_ROLE. As of 5.0.0, you can also use OPERATOR_ROLE and AUTHOR_ROLE. Default rights are assumed for any authenticated user. Therefore, if someone authenticates that should not have access, throw a com.carfey.suite.security.Authenticator.AuthenticationFailedException.

When assigning the user's com.carfey.suite.security.Roles, use the constructor public Role(String roleId, String roleName) using the appropriate constant for both the roleId and roleName. Role meanings are defined in User Rights.

Putting it All Together

Finance attempts to log in to Obsidian, gives valid credentials but should not be accessing Obsidian.

import static com.carfey.ops.Constant.*;

import com.carfey.suite.security.Authenticator.AuthenticationFailedException;
import com.carfey.suite.security.Role;
import com.carfey.suite.security.User;


public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "financeGuy"
    //pass is "mystrongpass"
    //credentials are valid, but user does not have any rights to Obsidian

    throw new AuthenticationFailedException(String.format("User [%s] is not authorized to use Obsidian Scheduler.", username));
}

Fred logs in using his username fredScheduler and his password badpass. You determine that his password is invalid.

import static com.carfey.ops.Constant.*;

import com.carfey.suite.security.Authenticator.AuthenticationFailedException;
import com.carfey.suite.security.Role;
import com.carfey.suite.security.User;


public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "fredScheduler"
    //pass is "badpass"
    //credentials are invalid

    throw new AuthenticationFailedException(String.format("User [%s] could not be authenticated.", username));
}

Fred logs in using his username fredScheduler and his password mystrongpassword. You determine that his password is valid and matches with the user and he has WRITE_ROLE rights.


public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "fredScheduler"
    //pass is "mystrongpassword"
    //credentials are valid, he has WRITE_ROLE

    User user = new User(username);
    user.setRoles(Arrays.asList(new Role(WRITE_ROLE, WRITE_ROLE)));
    return user;
}

Tina logs in using her username tinaOperator and her password mystrongpassword. You determine that her password is valid and matches with the user and she has Default rights.


public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "tinaOperator"
    //pass is "mystrongpassword"
    //credentials are valid, Tina has default access

    return new User(username);
}

The intern logs in using his username newGuy and his password mystrongpassword. You determine that his password is valid and matches with the user and he has LIMITED_READ_ROLE rights.


public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "newGuy"
    //pass is "mystrongpassword"
    //credentials are valid, the intern has LIMITED_READ_ROLE access

    User user = new User(username);
    user.setRoles(Arrays.asList(new Role(LIMITED_READ_ROLE, LIMITED_READ_ROLE)));
    return user;
}

The new hire logs in using her username newHire and her password is i am a new hire. You determine that her password is valid and matches with the user and she has OPERATOR_ROLE rights only for the job folder roots DevOps and FinOps.

private static final String DEV_OPS_OPERATOR = "DevOps-" + OPERATOR_ROLE;
private static final String FIN_OPS_OPERATOR = "FinOps-" + OPERATOR_ROLE;
public User authenticate(String username, String pass) throws AuthenticationFailedException {
    //usernameis "newHire"
    //pass is "i am a new hire"
    //credentials are valid, the new hire has OPERATOR_ROLE access for job folder roots DevOps and FinOps

   User user = new User(username);
   user.setRoles(Arrays.asList(new Role(DEV_OPS_OPERATOR, DEV_OPS_OPERATOR), new Role(FIN_OPS_OPERATOR, FIN_OPS_OPERATOR)));
   return user;
}

Customizing our LDAP Authenticator

com.carfey.suite.security.LdapAuthenticator is Obsidian's LDAP Authentication class that, when combined with its configurability that is documented here, meets most needs. But with the large variety of LDAP servers and potential implementations of dn strings, role and group definition and membership and even authentication methods supported, you may need to tweak its use somewhat. Rather than require you to write your own LDAP Authenticator, we have made efforts to make ours flexible enough to be specialized.

Specialization points

protected void buildUpContextEnvironment(String pass, String dn, Hashtable<String, String> environment)

Stores needed environment attributes for authentication using javax.naming.directory.InitialDirContext.

Currently stores:

Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"
Context.PROVIDER_URL, {{ldap_url}} 
Context.SECURITY_AUTHENTICATION, "simple" //or overridden securityAuthentication by configuration com.carfey.suite.security.LdapAuthenticator.securityAuthentication 
Context.SECURITY_PRINCIPAL, {{user_dn}} 
Context.SECURITY_CREDENTIALS, {{user_pass}}

protected boolean isMemberOfGroup(DirContext authContext, String groupName, String dn) throws NamingException

Checks the authenticated DirContext for group membership. Used to determine Default rights to Obsidian, in addition to its defined rights roles. Currently queries "uniquemember","uniqueMember","member","roleOccupant", "memberOf" and "MemberOf" attributes for any match on the dn.

Deploying your Authenticator

Once your authenticator class is written, you need to deploy the compiled class and any dependent libraries to your admin web application instances as JAR files. These should be placed under the /WEB-INF/lib directory of either the Obsidian web application directory or WAR file.