tomcat realm with database logins - tomcat9

Summary:
How would you validate a tomcat user providing the database(!) login credentials?
Details:
One thing I don't understand with tomcat is the realm config in combination with a relational database. It is clear what the JDBCRealm implementation does: Login to the database with a technical user, read the users table to find the login/password combination and these are the allowed logins. So the database has one technical user and a table.
But for a pure database frontend, the login should be validated by the database login mechanisms. In other words, the user provides the database username/password, then a new jdbc connection with this provided information is created and if successful, the provided login data is obviously correct. No technical database user, no table with a list of users.
Before I write that from scratch, is there something I overlooked? Does such a thing exist already?
Thanks in advance!
PS: Of course there are many cases where the database is just used as a directory of data. In my case I use the tomcat as frontend for a database application, with database users, database row level security, database permissions, database stored procedures, database...everything.
PPS: The follow up question will be connection pooling per-user. I have seen some hints on how to do that but any early input is greatly appreciated.

A few questions still but a REALM it is:
public class DBRealm extends RealmBase {
private static final Log log = LogFactory.getLog(DBRealm.class);
private String DBjdbcurl;
private Map<String, DBPrincipal> userdirectory = new HashMap<>();
public DBRealm() {
}
#Override
public DBPrincipal authenticate(String username, String credentials) {
log.info("Authenticating user \"" + username +
"\" with database \"" + DBjdbcurl + "\"");
try {
DBPrincipal principal = new DBPrincipal(username, credentials, DBjdbcurl);
// this does throw a SQLException in case the login data is invalid
userdirectory.put(username, principal);
return principal;
} catch (SQLException e) {
log.error("failed to login with the provided credentials for \"" +
username + "\" with database \"" + DBjdbcurl +
"\" and exception " + e.getMessage());
return null;
}
}
#Override
protected String getPassword(String username) {
return null;
// Do not expose the password. What is the side effect of that with md5 digest???
}
#Override
protected DBPrincipal getPrincipal(String username) {
return userdirectory.get(username);
}
/*
* Properties
*/
public String getDBJDBCURL() {
return DBjdbcurl;
}
public void setDBJDBCURL(String DBjdbcurl) {
this.DBjdbcurl = DBjdbcurl;
}
}

Related

AEM 6.3 Cannot create groups with service user

Hoping someone on here can help me out of a conundrum.
We are trying to remove all Admin sessions from our application, but are stuck with a few due to JCR Access Denied exceptions. Specifically, when we try to create AEM groups or users with a service user we get an Access Denied exception. Here is a piece of code written to isolate the problem:
private void testUserCreation2() {
String groupName = "TestingGroup1";
Session session = null;
ResourceResolver resourceResolver = null;
String createdGroupName = null;
try {
Map<String, Object> param = new HashMap<String, Object>();
param.put(ResourceResolverFactory.SUBSERVICE, "userManagementService");
resourceResolver = resourceResolverFactory.getServiceResourceResolver(param);
session = resourceResolver.adaptTo(Session.class);
// Create UserManager Object
final UserManager userManager = AccessControlUtil.getUserManager(session);
// Create a Group
LOGGER.info("Attempting to create group: "+groupName+" with user "+session.getUserID());
if (userManager.getAuthorizable(groupName) == null) {
Group createdGroup = userManager.createGroup(new Principal() {
#Override
public String getName() {
return groupName;
}
}, "/home/groups/testing");
createdGroupName = createdGroup.getPath();
session.save();
LOGGER.info("Group successfully created: "+createdGroupName);
} else {
LOGGER.info("Group already exists");
}
} catch (Exception e) {
LOGGER.error("Error while attempting to create group.",e);
} finally {
if (session != null && session.isLive()) {
session.logout();
}
if (resourceResolver != null)
resourceResolver.close();
}
}
Notice that I'm using a subservice name titled userManagementService, which maps to a user titled fwi-admin-user. Since fwi-admin-user is a service user, I cannot add it to the administrators group (This seems to be a design limitation on AEM). However, I have confirmed that the user has full permissions to the entire repository via the useradmin UI.
Unfortunately, I still get the following error when I invoke this code:
2020-06-22 17:46:56.017 INFO
[za.co.someplace.forms.core.servlets.IntegrationTestServlet]
Attempting to create group: TestingGroup1 with user fwi-admin-user
2020-06-22 17:46:56.025 ERROR
[za.co.someplace.forms.core.servlets.IntegrationTestServlet] Error
while attempting to create group. javax.jcr.AccessDeniedException:
OakAccess0000: Access denied at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(CommitFailedException.java:231)
at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(CommitFailedException.java:212)
at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.newRepositoryException(SessionDelegate.java:670)
at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate.java:496)
Is this an AEM bug, or am I doing something wrong here?
Thanks in advance
So it seems the bug is actually in the old useradmin interface. It was not allowing me to add my system user into the admninistrators group, but this is possible in the new touch UI admin interface.

Spring Boot: Who to write a custom Prefilter, considering the user role?

I need some kind of #preFilter (or, if it isnot possible than #postFilter) to filter the results of my REST API. I can not use the preFilteranotation, because I need to consider the user role. I have three different roles:
user the normal user, who shold only access data which he owns
teamleader this role should access all data of his team
admin who can access all data.
Because our database structure is really complex, it will be necessary, to access some other data, before I can decide if the user can access the requested data or parts of the requested data.
The snippet works only for the roles user and admin. For teamleader it will be more complex, then there will be a bunch of masterDataId which have to be connect with or.
Here is some pseudocode, hopefully its not to confusing:
public class RoleFilter {
DimensionAttributeValueRepository dimensionAttributeValueRepository;
public void doFilter(Collection<AllDatas> data) {
if (user.getRole() != "admin") {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
DimensionAttributeValue tmpValue = dimensionAttributeValueRepository.findByChrValue(auth.getUsername());
MasterData masterData = tmpValue.getMasterData();
data.filter(data.masterDataId == masterData.getMasterDataID());
}
}
}
Update: Example
Lets say I have two users, user A is a normal user with the role "user". User B is an admin with the role "admin".
There is a Database table, in which the userData are stored. The table looks like the following.
| ID | username | name | email |
Both of them are sending a simple authenticated GET request to /userData.
Now my backend detects based on the authentication header the users and add the roles.
Nwo depending on the role, the user A should only get an answere which contains his personal data, user B should get all data which are accessible though /userData.
Response for user A:
{
"res":[
{
"id":1,
"username":"userA",
"name":"A",
"email":"userA#mail.com"
}
]
}
Response for user B:
{
"res":[
{
"id":1,
"username":"userA",
"name":"A",
"email":"userA#mail.com"
},
{
"id":2,
"username":"userB",
"name":"B",
"email":"userB#mail.com"
},
{
"id":3,
"username":"userC",
"name":"C",
"email":"userC#mail.com"
}
]
}
For your usecase I would recommend to use a custom filter and integrate it into the spring-security filter chain. Here is a tutorial, explaining it in general. You could configure your custom filter so that it checks your complex roles against the database and then overwrite the current users authentication object with a new one.
Example implementation:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// HERE GOES YOUR CODE
// Depending on the extracted authentication details of the current user, you can now overwrite the users GrantedAuthorities
Collection<SimpleGrantedAuthority> oldAuthorities = (Collection<SimpleGrantedAuthority>)SecurityContextHolder.getContext().getAuthentication().getAuthorities();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_TEAMLEADER");
List<SimpleGrantedAuthority> updatedAuthorities = new ArrayList<SimpleGrantedAuthority>();
updatedAuthorities.add(authority);
updatedAuthorities.addAll(oldAuthorities);
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
SecurityContextHolder.getContext().getAuthentication().getPrincipal(),
SecurityContextHolder.getContext().getAuthentication().getCredentials(),
updatedAuthorities));
chain.doFilter(request, response);
}
}
Afterwards you can check for your roles with this statement: #PreAuthorize("hasRole('ROLE_TEAMLEADER')")
Then you can just access the users roles with the help of the spring-security-context object: SecurityContextHolder.getContext().getAuthentication().getAuthorities()
Based on its results you can now customize your answer upon the roles that are stored in this object. You could for example implement a RestCall on /userData like this:
#GetMapping("/userData")
public List<Object> getUserData() {
List<SimpleGrantedAuthority> roles = (List<SimpleGrantedAuthority>) SecurityContextHolder.getContext().getAuthentication().getAuthorities();
SimpleGrantedAuthority authorityTeamLeader = new SimpleGrantedAuthority("ROLE_TEAMLEADER");
List<Object> result = new ArrayList<>();
if (roles.contains(authorityTeamLeader)) {
result = getAllUsers();
} else {
result = getPersonalUser(roles);
}
return result;
}

How to prevent infinite retries - Apache Shiro RESTful service

Goal
I am setting up a RESTful webservice, using RESTEasy framework. For security I use Apache Shiro. I want my api to stop accepting requests or timing out persons that login too much.
Problem
Whenever I go some URL with my browser (chrome), I can try to login infinitely many times. Seems a really bad idea to allow this. As a measure, I have made sure to remember the nr of login attempts, for which users cannot login after 3 times. However, with a brute force attack, you could still block all users from loging in. I want a more general solution.
Shiro.ini
[main]
# We store users and passwords inside the realm.
myRealm = com.myproject.shiro.DatabaseRealm
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
[urls]
/api/version = anon
/api/** = authcBasic
DatabaseRealm
public class DatabaseRealm extends AuthorizingRealm {
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// No clue what to do with this functin. I only use authentication and not authorization, so probably just nothing.
return null;
}
/**
* Check if the user inputted is valid. The user can login if holds:
* 1. Password is correct. (if not, nrOfLogonAttempts++)
* 2. LogonUser.nrOfLogonAttemps is less than 3
* 3. LogonUser.dateEndValid is null or >= today.
* #param authenticationToken Token with basic information.
* #return SimpleAuthenticationInfo
* #throws AuthenticationException Whenever the user cannot login.
*/
#SuppressWarnings("ConstantConditions")
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws UnknownAccountException, IncorrectCredentialsException, LockedAccountException, ExpiredCredentialsException {
// Connect with the database.
DbContext context = null;
try {
context = DbContextUtil.getContextFromTomcat();
// Lookup user in the database.
LogonUserMyDao logonUserMyDao = new LogonUserMyDao(context);
LogonuserPojo logonuserPojo = logonUserMyDao.fetchOneByUsername(((UsernamePasswordToken) authenticationToken).getUsername());
if (logonuserPojo == null) {
throw new UnknownAccountException("Could not find user.");
}
// Check password
String plainTextPassword = new String(((UsernamePasswordToken) authenticationToken).getPassword());
if (!BCryptUtil.checkPassword(plainTextPassword, logonuserPojo.getPassword())) {
// We will note this event.
logonuserPojo.setNroflogonattempts(logonuserPojo.getNroflogonattempts() + 1);
logonUserMyDao.update(logonuserPojo);
context.commit();
throw new IncorrectCredentialsException("Incorrect password.");
}
// Check nrOfLogonAttempts
if (logonuserPojo.getNroflogonattempts() >= 2) {
throw new LockedAccountException("Cannot login anymore.");
}
// Check date
if (logonuserPojo.getDateendvalid() != null && DateTimeUtil.isBeforeToday(logonuserPojo.getDateendvalid())) {
throw new ExpiredCredentialsException("Account is expired.");
}
// User is valid, so return some info.
return new SimpleAuthenticationInfo(logonuserPojo.getUsername(), plainTextPassword, getClass().getName());
} catch (SQLException e) {
MyLogger.logError("Could not connect to user database.", e);
throw new AuthenticationException("Could not connect to databse.");
} finally {
if (context != null) {
try {
context.getConnection().close();
} catch (SQLException e) {
MyLogger.logError("Could not close connection", e);
}
}
}
}
}
Are you looking for more general DDOS protection? There are a few options out there depending on where your app is running (for example AWS Shield).
You could also prevent connections from reaching your db with something like this: https://github.com/iTransformers/ddos-servlet-filter (but, that that would still require handling the request in your application)
On the Shiro side of things, counting your attempts is NOT a bad idea, but you need to watch out for the user management side of things (How does a user get unlocked, support request? Wait 30 minutes?) Instead of recording failures, you may just want to record/audit all attempts (excluding the actual password of course). With either option a call to support or an n minute window, this may help provide some context to support or an easy query.

Not able to read VCAP_SERVICES in Bluemix

I used the following code to read my VCAP_SERVICES environment variable of Liberty application.
And I am not getting any values, result shows null or "not found".
private void readECaaSEnvVars() {
Map<?, ?> env = System.getenv();
Object vcap = env.get("VCAP_SERVICES");
if (vcap == null) {
System.out.println("No VCAP_SERVICES found");
}
else {
try {
JSONObject obj = new JSONObject(vcap);
String[] names = JSONObject.getNames(obj);
if (names != null) {
for (String name : names) {
if (name.startsWith("DataCache")) {
JSONArray val = obj.getJSONArray(name);
JSONObject serviceAttr = val.getJSONObject(0);
JSONObject credentials = serviceAttr.getJSONObject("credentials");
String username = credentials.getString("username");
String password = credentials.getString("password");
String endpoint=credentials.getString("catalogEndPoint");
String gridName= credentials.getString("gridName");
System.out.println("Found configured username: " + username);
System.out.println("Found configured password: " + password);
System.out.println("Found configured endpoint: " + endpoint);
System.out.println("Found configured gridname: " + gridName);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Your parsing code is OK.
In your Bluemix application dashboard, confirm you have the DataCache service bound to your application.
After a new service gets bound, you need to restage the application for the environment variable to get updated. cf restage <appname>
Output the environment variable to confirm DataCache credentials are in there System.out.println("VCAP_SERVICES: " + System.getenv("VCAP_SERVICES"));
You should also know that by default the the Liberty buildpack generates or updates existing server.xml file configuration stanzas for the Data Cache instance. The bound Data Cache instance can be accessed by the application using JNDI. The cache instance can either be injected into the application with an #Resource annotation, or can be looked up by the application with the javax.naming.InitialContext.
To see your server.xml on Bluemix for Liberty application:
cf files myLibertyApplication app/wlp/usr/servers/defaultServer/server.xml
You should see something like:
<xsBindings>
<xsGrid jndiName="wxs/myCache"
id="myCache"
gridName="${cloud.services.myCache.connection.gridName}"
userName="${cloud.services.myCache.connection.username}"
password="${cloud.services.myCache.connection.password}"
clientDomain="${cloud.services.myCache.name}"/>
</xsBindings>
where your JNDI name is wxs/myCache. This avoids the need for parsing VCAP_SERVICES.

Seam framework storing active user in session scobe

I want to store activeUser object in session scobe when someone logged in, I am using it forward for user specific operations but there is a problem accours when admin attemps to loggin because admin is not a default user so #out annotation causes an error
error code is familiar = #Out attribute requires non-null value: authenticator.activeUser
my logic code is below;
(note: admin and standart user use same login panel)
#Out(scope = ScopeType.SESSION, required = true)
private User activeUser;
public boolean authenticate() throws Exception {
// SELECT OBJECT(user) FROM User user WHERE user.name = :name and
// user.password = :pass
try {
// create a query for getting a user with specified parameters if no
// user exist throw an exception
setActiveUser((User) entityManager
.createQuery(
"select u from User u where u.userName= :uname and u.password = :pass")
.setParameter("uname", credentials.getUsername())
.setParameter("pass", credentials.getPassword())
.getSingleResult());
identity.addRole("user");
return true;
} catch (Exception e) {
// so there is no any normal user now looking for any admin entry
System.out.println("there is no any member looking for admin");
try {
Admin admin = (Admin) entityManager
.createQuery(
"select a from Admin a where a.username = :uname and a.pass = :p")
.setParameter("uname", credentials.getUsername())
.setParameter("p", credentials.getPassword())
.getSingleResult();
identity.addRole("admin");
System.err.println("found admin");
return true;
} catch (Exception e2) {
System.err.println("admin bulunamadı");
throw e2;
}
}
}
in #out required must be true because in user specific crud operations I am logging them with user id.
how can I solve this problem. setting required=false is enough?
and the other hand if no user finds the entitymanager.resultlist() method throws an exception. so i know that the #out annotataions works unless the methods throw any excepttion?