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?
Related
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.
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;
}
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.
I Implementd a custom userservice to store user data in a database.
Since that I get a NullReferenceException when I try to authenticace with an external provider like facebook.
I can see this stack in the exception which indicates that a value is missing for the loginpage. As A result after clicking on the facebook button i'm standing again at the starting login page.
But I don't know why or which value exactly
I can see that at the end of AuthenticateExternal context.AuthenticateResult.User.Claims contains these claims
my user service looks simplified like this
public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext context)
{
string id = context.ExternalIdentity.Claims.FirstOrDefault(i => i.Type == "id").Value;
var user = await gateway.ByExternalIds(context.ExternalIdentity.Provider, id);
if (user == null)
{
string displayName = context.ExternalIdentity.Claims.FirstOrDefault(i => i.Type.Equals("urn:facebook:name")).Value;
user = new User(context.ExternalIdentity);
await gateway.StoreAsync(user);
}
if (user != null)
{
await gateway.SetLastLogin(user.Subject, DateTimeOffset.Now);
context.AuthenticateResult = new AuthenticateResult(user.Subject, GetDisplayName(user), identityProvider: context.ExternalIdentity.Provider);
}
}
What am I missing?
Found it. This behavior occurs when the method for detemerning if the user ist active returns false.
Task IsActiveAsync(IsActiveContext context)
its little unexpected as I had expected that in this case i would see something like "unknown user" or "inactive user"
To check the permissions of the initiator you can call session.checkPermission()
However, com.day.cq.workflow.WorkflowSession.getSession() returns always the admin session so I'm not able to check the initator's permissions on a given node.
How can I get the session of the initator?
Update
Authorizable authorizable = userManager.getAuthorizable(initiator);
Credentials credentials = ((User) authorizable).getCredentials();
Session userSession = adminSession.impersonate(credentials);`
Throws:
javax.jcr.LoginException: Login Failure: all modules ignored
at org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl.login(RepositoryImpl.java:271)
at com.adobe.granite.repository.impl.CRX3RepositoryImpl.login(CRX3RepositoryImpl.java:92)
at org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl.login(RepositoryImpl.java:202)
at org.apache.jackrabbit.oak.jcr.session.SessionImpl.impersonate(SessionImpl.java:284)
Caused by: javax.security.auth.login.LoginException: Login Failure: all modules ignored
at javax.security.auth.login.LoginContext.invoke(LoginContext.java:906)
at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195)
at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682)
... 15 common frames omitted`
First, as #CptBartender mentioned in the comments, the admin should have rights to all content or if you're taking advantage of the Service Accounts, that service account should have access to the content that it needs to update.
The initiator is always admin or workflow-service in newer AEM versions that use Sling service accounts. The user requests the start of the workflow and the admin or service account runs the process. If you want to find the user who kicked off the workflow you can look in the meta data with item.getWorkflowData().getMetaDataMap().get("userId", String.class). Notice that data matches the resources created in the JCR at a path similar to /etc/workflow/instances/server0/2016-06-13/update_asset_2/data/metaData. Furthermore, you can get the participant of individual workflow process steps by getting the HistoryItem and then the userId.
With the initiator determined, you should be able to impersonate with something such as:
#Component
#Service
#Properties({
#Property(name = Constants.SERVICE_DESCRIPTION, value = "Workflow step description"),
#Property(name = Constants.SERVICE_VENDOR, value = "Company Name"),
#Property(name = "process.label", value = "Process Label will show in the workflow dropdown") })
public class MyCustomStep implements WorkflowProcess {
public void execute(WorkItem item, WorkflowSession wfsession, MetaDataMap args) throws WorkflowException {
/* Always admin or service-workflow */
final String initiator = item.getWorkflow().getInitiator();
/* Get actual user who initiated workflow */
final String initiator = item.getWorkflowData().getMetaDataMap().get("userId", String.class);
/* Get workflow history */
final List<HistoryItem> histories = wfsession.getHistory(item.getWorkflow());
/* Get first item in workflow history */
final HistoryItem firstItem = histories.get(0);
/* Get the user that participated in the last item */
final String firstUser = firstItem.getUserId();
/* Get impersonated session */
try {
Session userSession = wfsession.getSession().impersonate(new SimpleCredentials(initiator,new char[0]));
} catch (javax.jcr.LoginException e) {
e.printStackTrace();
} catch (RepositoryException e) {
e.printStackTrace();
}
}
}