Lock a page with current loggedin user in CQ - aem

I have been working on a code to lock a page with the current logged-in user (author). I have attached a listener on the property changed event. The page is getting locked as admin instead of the current user.
Below is the code snippet that i am using to lock the page.
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
Resource res = resourceResolver.getResource(lockablePagePath);
if(res != null) {
LockManager lockManager = jrSession.getWorkspace().getLockManager();
Node lockableNode = jrSession.getNode(lockablePagePath + "/jcr:content");
if (lockManager.isLocked(lockableNode.getPath())) {
LOG.error("Page/Node is alrady Locked by ");
} else {
lockManager.lock(lockableNode.getPath(), true, false, 1000, userId);
//jrSession.save();
}
}
Kindly suggest.

You are mixing Sling (ResourceResolver) and JCR (Session) API here. Based on my experience I would recommend sticking to one (preferably Sling, as it's a higher level of abstraction).
The jrSession you're using most likely belongs to the admin user. You might need to impersonare (Session.impersonate(Credentials creds)) the user you want to lock the page.
Using Sling API, I would do something like this:
Map<String, Object> map = new HashMap<String, Object>();
map.put(ResourceResolverFactory.USER_IMPERSONATION, userId);
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(map);
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
Page page = pageManager.getPage(lockablePagePath);
if (page.isLocked()) {
//log error
} else {
page.lock();
}
What I do here is, I retrieve an administrative resourceResolver that impersonates a given (current) user.
I've never tried it, but that's the way I'd approach the problem given the documentation.

Related

Automate user creation and deletion through external API requests

I have 0 experience in coding in APEX so I would greatly appreciate your help and support with this question!
I would like to figure out a way to automate the deletion of an Aircall User if an SF user is deleted. Let us assume that every SF user has an Aircall ID that is present in their User profiles, stored in a field called 'Aircall ID'. This is what I will need to form the delete request.
I want that when a user is deleted on Salesforce, it triggers a delete request to Aircall sending the value that was previously stored in the Aircall ID field to the specific endpoint in question.
I need help figuring out how to write an APEX trigger that sends the Aircall ID to the class (to be triggered after the user is deleted) and finally how to automatically trigger the execution of this class after the ID has been received in order to complete the User deletion on Aircall's platform.
public class deleteAirCallUser {
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setMethod('DELETE');
string encodedCredentials = 'apikey';
String authorizationHeader = 'Basic ' + encodedCredentials;
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
request.setHeader('Authorization', authorizationHeader);
string AircallUserId = //should be the Aircall userID from the deleted profile
request.setBody(AircallUserId);
request.setEndpoint('https://api.aircall.io/v1/users/'+ Aircall userID);
HttpResponse response = http.send(request);
if (response.getStatusCode() == 200) {
Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
System.debug(results);}
else{
Map<String, Object> results_2 = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
System.debug(results_2);
}
}
Thank you for your help!
https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_user.htm
"You can’t delete a user in the user interface or the API. You can deactivate a user in the user interface; and you can deactivate or disable a Customer Portal or partner portal user in the user interface or the API. Because users can never be deleted, we recommend that you exercise caution when creating them."
For deactivations you'll need something like this. (It's not written to best practices, ideally the trigger would be "thin" and actual processing offloaded to helper class. Also it assumes you mass update max 10 users at a time because that's the limit of callouts.
trigger UserTrigger on User (after update){
Set<String> toSend = new Set<String>();
for(User u : trigger.new){
User oldUser = trigger.oldMap.get(u.Id);
// have we deactivated them?
if(!u.isActive && oldUser.isActive && String.isNotBlank(u.AirCallId__c)){
toSend.add(u.AirCallId__c);
}
}
if(!toSend.isEmpty()){
sendAirCallDeletes(toSend);
}
// This should be in a helper class, it looks bizarre to have functions defined in trigger's body
#future
static void sendAirCallDeletes(Set<String> toSend){
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setMethod('DELETE');
String encodedCredentials = 'apikey';
String authorizationHeader = 'Basic ' + encodedCredentials;
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
request.setHeader('Authorization', authorizationHeader);
for(String airCallId : toSend){
request.setBody(airCallId);
request.setEndpoint('https://api.aircall.io/v1/users/'+ airCallId);
try{
HttpResponse response = http.send(request);
System.debug(response.getStatusCode());
System.debug(response.getBody());
System.debug((Map<String, Object>) JSON.deserializeUntyped(response.getBody());
} catch(Exception e){
System.debug(e);
}
}
}
}
You might want to read up about "named credentials" (don't store the api keys etc in code), why we need "#future" trick when we want to make callout from a trigger, how to check for limit of calls you can make in single transaction... But should be a start?

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.

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.

ServiceStack OAuth - registration instead login

In servicestack OAuth implementation I only saw possibility to automatically login with eg. facebook account.
But is there abbility to support registration process with facebook login. What I wanted is to let users login to facebook app, and then load their Name, Surname and email and prefill needed text boxes for real registration on my site (since I also have to have mobile phone verification etc.) I don't want user to be authorized and authenticated when he logs in with facebook. Only credentials login should be valid one for full site access.
Edit: I found a solution.
In FacebookProvider.cs
public override bool IsAuthorized(IAuthSession session, IOAuthTokens tokens, Auth request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName)) return false;
}
return tokens != null && session.UserName!=null && !string.IsNullOrEmpty(tokens.AccessTokenSecret);
}
The catch was the && session.UserName!=null part. So we can check if user is logged in using credentials, this will be !=null and user can use all services. If not, this will be ==null and he can only get facebook info from session.
The SocialBootstrap API project shows an example of handling the callback after a successful Authentication by overriding the OnAuthenticated() hook of its custom user session:
I've pulled out, rewrote some and highlighted some of the important bits:
public class CustomUserSession : AuthUserSession
{
public override void OnAuthenticated(IServiceBase authService,
IAuthSession session,
IOAuthTokens tokens,
Dictionary<string, string> authInfo)
{
base.OnAuthenticated(authService, session, tokens, authInfo);
//Populate matching fields from this session into your own MyUserTable
var user = session.TranslateTo<MyUserTable>();
user.Id = int.Parse(session.UserAuthId);
user.GravatarImageUrl64 = CreateGravatarUrl(session.Email, 64);
foreach (var authToken in session.ProviderOAuthAccess)
{
if (authToken.Provider == FacebookAuthProvider.Name)
{
user.FacebookName = authToken.DisplayName;
user.FacebookFirstName = authToken.FirstName;
user.FacebookLastName = authToken.LastName;
user.FacebookEmail = authToken.Email;
}
else if (authToken.Provider == TwitterAuthProvider.Name)
{
user.TwitterName = authToken.DisplayName;
}
}
//Resolve the DbFactory from the IOC and persist the user info
using (var db = authService.TryResolve<IDbConnectionFactory>().Open())
{
//Update (if exists) or insert populated data into 'MyUserTable'
db.Save(user);
}
}
//Change `IsAuthorized` to only verify users authenticated with Credentials
public override bool IsAuthorized(string provider)
{
if (provider != AuthService.CredentialsProvider) return false;
return base.IsAuthorized(provider);
}
}
Basically this user-defined custom logic (which gets fired after every successful authentication) extracts data from the UserSession and stores it in a custom 'MyUserTable'.
We've also overridden the meaning of IsAuthorized to only accept users that have authenticated with CredentialsAuth.
You can use this data to complete the rest of the registration.
Other possible customizations
ServiceStack's built-in Auth persists the AuthData and populates the Session automatically for you. If you want to add extra validation assertions you can simply use your own custom [Authentication] attribute instead containing additional custom logic. Look at the implementation of the built-in AuthenticateAttribute as a guide.

How to get the complete request that calls my MVC2 controller?

Newbie question … sorry ;-)
I have to write and to integrate a new website in a complex web application.
My new (MVC2) website will be hosted on a separate server and only called when the user clicks on a link in the already existing, complex website.
Means I(!) define the URL which calls my(!) new website.
But “they” (the calling, already existing, complex web application/website) will add an attribute to the url. This attribute is the sessionID.
Ok, I think I understand already that this calls my (MVC2) controller.
But how can I get in my (MVC2) controller the “calling URL” (which include the added sessionID)?
Hopefully that someone understand what I ask ;-)
Thanks in advance!
I want just share my little parser - hopefully it helps someone. ;-)
Also requests like
(Request.Url.Query =) "?sessionID=12345678901234567890123456789012&argumentWithoutValue&x=1&y&z=3"
will be well parsed.
Here my code:
Hashtable attributes = new Hashtable();
string query = Request.Url.Query;
string[] arrPairs = query.Split('&'); // ...?x=1&y=2
if (arrPairs != null)
{
foreach(string s in arrPairs)
{
if (!String.IsNullOrEmpty(s))
{
string onePair = s.Replace("?", "").Replace("&", "");
if (onePair.Contains("="))
{
string[] arr = onePair.Split('=');
if (arr != null)
{
if (arr.Count() == 2)
{
attributes.Add(arr[0], arr[1]);
}
}
}
else
{
// onePair does not contain a pair!
attributes.Add(onePair, "");
}
}
}
You really should set your URL and Route to be more MVC-Like. The URL you are calling should be:
newapp/controller/action/sessionId
Then set your route up:
routes.MapRoute(
"sessionId",
"{controller}/{action}/{sessionId}",
new { controller = "controller", action = "action", sessionId = 0 });
Then in your controller:
public ActionResult Action(int sessionId)
{
}
In your controller you still have direct access to the Request object, so you can use Request.Url, etc.
Does that answer your question, or is it something else that you need?