Design issue in Spring app: in which layer (web, service or repository) should I retrieve the currently logged in user? - spring

I work on a CRUD Spring app. Let me explain a basic use case:
A user can save an Advertisement. As of now I retrieve the currently logged in member in the web/controller layer and then pass it on to the service layer so that it can be set on the advertisement (the currently logged in user is the owner of the Advertisement; it is retrieve using Spring Security and my custom annotation: #CurrentMember).
In controller layer:
#RequestMapping(value = "/family/new", method = RequestMethod.POST, produces = "text/html")
public String newFamilyAdvertisement(
#ModelAttribute("advertisementInfo") #Validated(value = ValidationGroups.AdvertisementCreation.class) FamilyAdvertisementInfo familyAdvertisementInfo,
BindingResult bindingResult, Model model, #CurrentMember Member member) {
if (bindingResult.hasErrors()) {
populateFamilyAdvertisementModel(model, familyAdvertisementInfo, member);
return "advertisement/family/new";
}
advertisementService.createAdvertisement(member, familyAdvertisementInfo.getAdvertisement(), familyAdvertisementInfo.getAddressReference());
return "redirect:/advertisement/family/new";
}
In service layer:
#Override
public void createAdvertisement(Member member, Advertisement advertisement, String addressReference) {
if (member == null || advertisement == null || addressReference == null || addressReference.isEmpty()) {
throw new IllegalArgumentException("One argument is null or empty");
}
Address address = geolocationService.retrieveAddressFromReference(addressReference);
advertisement.setAddress(address);
advertisement.setMember(member);//SET CURRENTLY LOGGED IN USER
advertisement.setValidated(Boolean.FALSE);
advertisement.setActive(Boolean.TRUE);
advertisement.setCreationDate(utils.now());
saveAdvertisement(advertisement);
}
Still in service layer(Roo ITD):
public void AdvertisementServiceImpl.saveAdvertisement(Advertisement advertisement) {
advertisementRepository.save(advertisement);
}
Now the interrogation I have is:
Should I retrieve the current user/member as early as possible (here in the web layer) and then pass it on until it is needed (here in the service layer)? OR
Should I retrieve the current user/member only when I need it (here in the service layer)?

Thats a matter of design and choices you need to made, usually you dont need to bother controller to pass member to service. It doesnt need any knowledge about user. You can easily load it in service so you api is shorter/cleaner.
But, in a case your api is used from some external project - then api should show what objects are needed to make it work.
To sum up, in your case I would load it in service.

Related

#Cacheable is not able to update

In spring framework we have #Cacheable to cache data right. Now my requirement is i want to retrieve all data form database by using Get method.
Controller
#RequestMapping(value = "/getUploadData", method = RequestMethod.GET)
public ResponseEntity<List<Ticket>> getUploadFileData() throws IOException {
return new ResponseEntity<>(ticketBookingService.getFileUploadData(), HttpStatus.OK);
}
Service
#Cacheable(value="ticketsCache")
public List<Ticket> getFileUploadData() {
List<Ticket> listOfData = (List<Ticket>) ticketBookingDao.findAll();
return listOfData;
}
}
output:
click image here to check output
http://localhost:8080/api/tickets/getUploadData
[{"ticketId":1,"passengerName":"Sean","bookingDate":1502649000000,"sourceStation":"Pune","destStation":"Mumbai","email":"sean.s2017#yahoo.com"},{"ticketId":2,"passengerName":"Raj","bookingDate":1502476200000,"sourceStation":"Chennai","destStation":"Mumbai","email":"raj.s2007#siffy.com"},{"ticketId":3,"passengerName":"Martin","bookingDate":1502735400000,"sourceStation":"Delhi","destStation":"Mumbai","email":"martin.s2001#xyz.com"},{"ticketId":4,"passengerName":"John","bookingDate":1503253800000,"sourceStation":"Chennai","destStation":"Mumbai","email":"john.s2011#yahoo.com"}]
Now i will do get and put operation by ticketid.
Get:
Controller:
#GetMapping(value="/ticket/{ticketId}")
public Ticket getTicketById(#PathVariable("ticketId")Integer ticketId){
return ticketBookingService.getTicketById(ticketId);
}
Service:
#Cacheable(value="ticketsCache",key="#ticketId",unless="#result==null")
public Ticket getTicketById(Integer ticketId) {
return ticketBookingDao.findOne(ticketId);
}
http://localhost:8080/api/tickets/ticket/1
{"ticketId":1,"passengerName":"Sean","bookingDate":1502649000000,"sourceStation":"Pune","destStation":"Mumbai","email":"sean.s2017#yahoo.com"}
Now when i do update email by using ticketid:
Put: controller
#PutMapping(value="/ticket/{ticketId}/{newEmail:.+}")
public Ticket updateTicket(#PathVariable("ticketId")Integer ticketId,#PathVariable("newEmail")String newEmail){
return ticketBookingService.updateTicket(ticketId,newEmail);
}
Service:
#CachePut(value="ticketsCache",key="#ticketId")
public Ticket updateTicket(Integer ticketId, String newEmail) {
Ticket upadedTicket = null;
Ticket ticketFromDb = ticketBookingDao.findOne(ticketId);
if(ticketFromDb != null){
ticketFromDb.setEmail(newEmail);
upadedTicket = ticketBookingDao.save(ticketFromDb);
}
return upadedTicket;
}
http://localhost:8080/api/tickets/ticket/1/abcd#yahoo.com
{
"ticketId": 1,
"passengerName": "Sean",
"bookingDate": 1502649000000,
"sourceStation": "Pune",
"destStation": "Mumbai",
"email": "abcd#yahoo.com"
}
Now when get data by using ID changes are updating.
http://localhost:8080/api/tickets/ticket/1
{"ticketId":1,"passengerName":"Sean","bookingDate":1502649000000,"sourceStation":"Pune","destStation":"Mumbai","email":"abcd#yahoo.com"}
Now my Question is if i try to get all data by using above first URL my changes are not reflecting.
http://localhost:8080/api/tickets/getUploadData
[{"ticketId":1,"passengerName":"Sean","bookingDate":1502649000000,"sourceStation":"Pune","destStation":"Mumbai","email":"sean.s2017#yahoo.com"},{"ticketId":2,"passengerName":"Raj","bookingDate":1502476200000,"sourceStation":"Chennai","destStation":"Mumbai","email":"raj.s2007#siffy.com"},{"ticketId":3,"passengerName":"Martin","bookingDate":1502735400000,"sourceStation":"Delhi","destStation":"Mumbai","email":"martin.s2001#xyz.com"},{"ticketId":4,"passengerName":"John","bookingDate":1503253800000,"sourceStation":"Chennai","destStation":"Mumbai","email":"john.s2011#yahoo.com"}]
Suggest me how to reslove this issue
You cannot bulk update the cache with Spring.
Please check the following issue - closed with status declined:
Thanks for creating the issue but I am not keen to add this extra complexity to the cache abstraction. It is not meant to manage state for you (the next logical step if we allow this is that we have to keep the returned list in sync with each item). And if we don't we are inconsistent and we merely provide a way to talk to the cache using annotations. That's not very helpful.
Back to your example, this is typically what a second level cache is meant to do for you. This is not in the scope of the cache abstraction.

How to add/manage user claims at runtime in IdentityServer4

I am trying to use IdentityServer4 in a new project. I have seen in the PluralSight video 'Understanding ASP.NET Core Security' that IdentityServer4 can be used with claims based security to secure a web API. I have setup my IdentityServer4 as a separate project/solution.
I have also seen that you can add an IProfileService to add custom claims to the token which is returned by IdentityServer4.
One plan is to add new claims to users to grant them access to different parts of the api. However I can't figure out how to manage the claims of the users on the IdentityServer from the api project. I assume I should be making calls to IdentotyServer4 to add and remove a users claims?
Additionally is this a good approach in general, as I'm not sure allowing clients to add claims to the IdentityServer for their own internal security purposes makes sense - and could cause conflicts (eg multiple clients using the 'role' claim with value 'admin'). Perhaps I should be handling the security locally inside the api project and then just using the 'sub' claim to look them up?
Does anyone have a good approach for this?
Thanks
Old question but still relevant. As leastprivilege said in the comments
claims are about identity - not permissions
This rings true, but identity can also entail what type of user it is (Admin, User, Manager, etc) which can be used to determine permissions in your API. Perhaps setting up user roles with specific permissions? Essentially you could also split up Roles between clients as well for more control if CLIENT1-Admin should not have same permissions as CLIENT2-Admin.
So pass your Roles as a claim in your IProfileService.
public class ProfileService : IProfileService
{
private readonly Services.IUserService _userService;
public ProfileService(Services.IUserService userService)
{
_userService = userService;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
switch (context.Client.ClientId)
{
//setup profile data for each different client
case "CLIENT1":
{
//sub is your userId.
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
//get the actual user object from the database
var user = await _userService.GetUserAsync(long.Parse(userId.Value));
// issue the claims for the user
if (user != null)
{
var claims = GetCLIENT1Claims(user);
//add the claims
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
break;
case "CLIENT2":
{
//...
}
}
}
catch (Exception ex)
{
//log your exceptions
}
}
// Gets all significant user claims that should be included
private static Claim[] GetCLIENT1Claims(User user)
{
var claims = new List<Claim>
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, user.Name),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_other_claim", user.Some_Other_Info ?? "")
};
//----- THIS IS WHERE ROLES ARE ADDED ------
//user roles which are just string[] = { "CLIENT1-Admin", "CLIENT1-User", .. }
foreach (string role in user.Roles)
claims.Add(new Claim(JwtClaimTypes.Role, role));
return claims.ToArray();
}
}
Then add [Authorize] attribute to you controllers for your specific permissions. This only allow specific roles to access them, hence setting up your own permissions.
[Authorize(Roles = "CLIENT1-Admin, CLIENT2-Admin, ...")]
public class ValuesController : Controller
{
//...
}
These claims above can also be passed on authentication for example if you are using a ResourceOwner setup with custom ResourceOwnerPasswordValidator. You can just pass the claims the same way in the Validation method like so.
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetClaims(user));
So like leastprivilege said, you dont want to use IdentityServer for setting up permissions and passing that as claims (like who can edit what record), as they are way too specific and clutter the token, however setting up Roles that -
grant them access to different parts of the api.
This is perfectly fine with User roles.
Hope this helps.

RedirectAttributes in Spring MVC 3.2.8

I have a project based in Spring Web model-view-controller (MVC) framework. The version of the Spring Web model-view-controller (MVC) framework is 3.2.8.
I have this method
#RequestMapping(value = { "/newdesign/manage/device/award",
"/newdesign/manage/device/award/"}, method = {RequestMethod.POST})
public String awardDeviceProduct(
#ModelAttribute("deviceForm") DeviceForm deviceForm,
HttpServletRequest request,
Model model,
final RedirectAttributes redirectAttributes) throws Exception {
checkUser (request, UserRole.MARKETING);
Device device = manageLicenseService.getDeviceById(deviceForm.getDevice().getId());
if (deviceForm.getDevice().getIos()==null) {
model.addAttribute ("errorMessage", "Licence Number cannot be null !");
redirectAttributes.addFlashAttribute("errorMessage", "Licence Number cannot be null !");
} else if (deviceForm.getSelectedItems()!=null &&
!deviceForm.getSelectedItems().isEmpty()) {
// check LICENCE DUPLICATED
manageLicenseService.applyStatusChange (device, deviceForm.getSelectedItems(), Status.AWARDED );
} else {
model.addAttribute ("errorMessage", "no Items selected !");
model.addAttribute ("productGroup", getNotExpiredProductGroups (request));
}
return "redirect:/newdesign/manage/device/" + deviceForm.getDevice().getId();
}
But in the JSP I can't find the attribute "errorMessage" !!!! when (deviceForm.getDevice().getIos()==null)
The problem with your code relies in the difference between "redirect" and "forward".
If you return with a redirect statement, the response will first return to the browser, and then it will request the new url. The problem with this approach is, that the redirected new request will have a completely new context, and will not have access to the Model, set in your previous response.
The forward response however is processed by the server side itself, transferring the request to the new URL. It is faster and the context can be maintained.
You can find more details here

How to maintain session information across authentication

I using ServiceStack authentication with a custom session object. I've got everything set up with different authentication providers and everything is working fine.
Now a want to store some information in the session before the user is authenticated (Think shopping cart). But we loose that information when the user logs in later. Looking at the code in the documentation this makes sense:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(), //Sign-in with Basic Auth
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
The authentication removes the existing session whenever a user logs in. This makes sense when the old login is a valid user, you want to make sure it's fully logged out. However when the current session isn't authenticated there doesn't seem to be much reason to do so.
I've been looking at a custom session factory, but that doesn't help me because as () => new AuthUserSession() shows, there isn't any context to use when creating the new session. Without a way to get the old session there I've got no way to copy any information.
I can work around it by overriding AuthProvider.Authenticate() and grab the required information before calling base. But that means doing so in every authentication provider we use and the ones we might use in the future. That doesn't really feel like the correct solution.
Is there a cleaner way to carry information across the authentication? Preferably something which works regardless of the AuthProvider used.
Whilst the Typed Sessions are re-created after authenticating, the Permanent and Temporary Session Ids themselves remain the same which lets you use ServiceStack's dynamic SessionBag to store information about a user which you can set in your Services with:
public class UnAuthInfo
{
public string CustomInfo { get; set; }
}
public class MyServices : Service
{
public object Any(Request request)
{
var unAuthInfo = SessionBag.Get<UnAuthInfo>(typeof(UnAuthInfo).Name)
?? new UnAuthInfo();
unAuthInfo.CustomInfo = request.CustomInfo;
SessionBag.Set(typeof(UnAuthInfo).Name, unAuthInfo);
}
}
You can then access the dynamic Session Bag in your Custom AuthUserSession Session Events with:
public class CustomUserSession : AuthUserSession
{
[DataMember]
public string CustomInfo { get; set; }
public override void OnAuthenticated(IServiceBase service, IAuthSession session,
IAuthTokens tokens, Dictionary<string, string> authInfo)
{
var sessionBag = new SessionFactory(service.GetCacheClient())
.GetOrCreateSession();
var unAuthInfo = sessionBag.Get<UnAuthInfo>(typeof(UnAuthInfo).Name);
if (unAuthInfo != null)
this.CustomInfo = unAuthInfo.CustomInfo;
}
}
New Session API's in v4.0.32+
Accessing the Session bag will be a little nicer in next v4.0.32+ of ServiceStack with the new GetSessionBag() and convenience ISession Get/Set extension methods which will let you rewrite the above like:
public object Any(Request request)
{
var unAuthInfo = SessionBag.Get<UnAuthInfo>() ?? new UnAuthInfo();
unAuthInfo.CustomInfo = request.CustomInfo;
SessionBag.Set(unAuthInfo);
}
//...
public override void OnAuthenticated(IServiceBase service, IAuthSession session,
IAuthTokens tokens, Dictionary<string, string> authInfo)
{
var unAuthInfo = service.GetSessionBag().Get<UnAuthInfo>();
if (unAuthInfo != null)
this.CustomInfo = unAuthInfo.CustomInfo;
}

How can I show Authenticated but UNAUTHORIZED users an unauthorized page MVC 3?

I have an application where some users belong to a Role, but may not actually have access to certain data within a URL. For instance the following url is open to all users
/Library/GetFile/1
However, some users may not have access to file1, but I can't use the Authorize attribute to detect that. I want instead to redirect those users to an unauthorized or accessdenied page. I'm using Forms Authentication and my config is set up like this
<authentication mode="Forms">
<forms loginUrl="~/Home/Index" timeout="2880" />
</authentication>
my custom errors block is like this
<customErrors mode="On" defaultRedirect="Error" redirectMode="ResponseRewrite" >
<error statusCode="401" redirect="Unauthorized"/>
</customErrors>
I am attempting to return the HttpUnauthorizedResult if the user does not have access, but I just get redirected to the login page, which isn't valid here because the User is Authenticated already.
It appears that the HttpUnauthorizedResult is setting the HTTP Response Code to 401 which Forms Authentication is hijacking and sending the user to the Login page.
Throwing the UnauthorizedAccessException doesn't seem to work either always redirecting the user to an IIS Error page even though I've updated my RegisterGlobalFilters to
filters.Add(new HandleErrorAttribute
{
ExceptionType = typeof(UnauthorizedAccessException),
View = "Unauthorized",
Order = 3
});
If I change UnauthorizedAccessException to a custom Exception the redirect works and for now that's what I've done.
Your solution is similar to mine except that I did this:
Create a custom exception, UnauthorizedDataAccessException.
Create a custom exception filter (so that it could log the invalid access attempt).
Register my custom exception attribute as a global filter in App_start.
Create a marker interface, ISecureOwner and added it to my entity.
Add a secure 'Load' extension method to my repository, which throws the exception if the current user is not the owner of the entity that was loaded. For this to work, entity has to implement ISecureOwner that returns the id of the user that saved the entity.
Note that this just shows a pattern: the details of how you implement GetSecureUser and what you use to retrieve data will vary. However, although this pattern is okay for a small app, it is a bit of hack, since that kind of security should be implemented deep down at the data level, using ownership groups in the database, which is another question :)
public class UnauthorizedDataAccessException : Exception
{
// constructors
}
public class UnauthorizedDataAccessAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == Typeof(UnauthorizedDataAccessException))
{
// log error
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Error", action = "UnauthorizedDataAccess" }));
}
else
{
base.OnException(filterContext);
}
}
// marker interface for entity and extension method
public interface ISecureOwner
{
Guid OwnerId { get; }
}
// extension method
public static T SecureFindOne<T>(this IRepository repository, Guid id) where T : class, ISecureOwner, new()
{
var user = GetSecureUser();
T entity = repository.FindOne<T>(id);
if (entity.OwnerId != user.GuidDatabaseId)
{
throw new UnauthorizedDataAccessException(string.Format("User id '{0}' attempted to access entity type {1}, id {2} but was not the owner. The real owner id is {3}.", user.GuidDatabaseId, typeof(T).Name, id, entity.OwnerId));
}
return entity;
}
// Register in global.asax
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
var filter = new UnauthorizedDataAccessAttribute { ExceptionType = typeof(UnauthorizedDataAccessException) };
filters.Add(filter);
filters.Add(new HandleErrorAttribute());
}
// Usage:
var ownedThing = myRepository.SecureFindOne<myEntity>(id))
You can restrict access to certain roles. If an unauthorized role tries to access a resource you can redirect them to a specific url.
Look at this other SO question: attribute-for-net-mvc-controller-action-method, there are good answers there.
You can check in your code if a user belongs to a role:
User.IsInRole("RoleToTest");
you can also apply attributes to your controllers/action methods. Anyhow it is all explained in the link I specified above.
* EDIT *
You could override OnException in your base Controller. Implement a custom exception, e.g., AccessNotAuthorizedAccessException.
In OnExcepton, if you detect your custom exception, just redirect to a friendly url that shows the 'Not authorized...' message.

Resources