Jhipster: hide entities from non-admin - spring-security

Greetings java hipsters!
I just generated a jhipster project and created some entities. I'd like to hide some entities by restricting them to only the admin user. How do I achieve this ?
Thanks!

First read Spring Security doc then look at your project source code that was generated by JHipster: it's full of such examples, pay attention to:
SecurityConfiguration.java
#Secured(AuthoritiesConstants.ADMIN) in UserResource.java
Then for the angular part, you can add a requirement for admin role in a state's definition like in src/main/webapp/app/admin/configuration/configuration.state.js (search for authorities: ['ROLE_ADMIN']). So for a bank-account entity, main state would be defined in src/main/webapp/app/entities/bank-account/bank-account.state.js.
This is for JHipster 3.x

I just describe how i blocked new entity("folder") on a bit more fresh version (JHipster 4.7.0):
to block access to endpoint I added new line in a file: src/main/java/package path/config/SecurityConfiguration.java:
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/folders").hasAuthority(AuthoritiesConstants.ADMIN) //new line
.antMatchers("/api/**").authenticated()
change src/main/webapp/app/entities/folder/folder.route.ts:
data: {
authorities: ['ROLE_USER'], // old
authorities: ['ROLE_ADMIN'],// new
pageTitle: 'jmediaApp.folder.home.title'
},
and to hide item from navbar you need to add *jhiHasAnyAuthority="'ROLE_ADMIN'" in <li> tag in /src/main/webapp/app/layouts/navbar/navbar.component.html:
<li *jhiHasAnyAuthority="'ROLE_ADMIN'">

On the Gateway UI By Using react.js server following process can be followed.
Open src/main/webapp/app/app.tsx which contains all routing mechanism. Check the component <Header ..{additional props added}.. />
on mapStateToProps there is a function by the name isAdmin to check if the logged-in user is an admin. (Change this according to your ROLE. I am using ROLE_ADMIN so I left as it is.)
Go to src/main/webapp/app/shared/layout/header/header.tsx. This file contains all the navigation to the entities under <EntitiesMenu > component. pass the isAdmin prop to the EntitiesMenu component as <EntitiesMenu admin={props.isAdmin} />
Now Go to src/main/webapp/app/shared/layout/menus/entities.tsx change the respective path to
{
props.isAdmin && (
<MenuItem icon="asterisk" to="/entity/institute">
<Translate contentKey="global.menu.entities.institute"
/>
</MenuItem> )
}
This helps in hiding the navigation links.
But if the user enters the specific path on the browser url, There is no validation on that. So change src/main/webapp/app/entities/index.tsx from <ErrorBoundaryRoute ../> to
<PrivateRoute path={${match.url}/institute} component={Institute} hasAnyAuthorities={[AUTHORITIES.ADMIN]} />
After this even if the user open the URL by mentioning it in the browser search bar, Jhipster validates if the user has got the role or not. If not simply access denied page will be shown.

Related

ASP.NET Core 3.1 MVC Routing issue with IIS Default Web Site

New information:
After much messing around with trying to manipulate the URL I almost got it working but not quite. Then I discovered that it works without any coding changes if my home page url is \\localhost\ABIAdmin\Home. But it starts as \\localhost\ABIAdmin, and I have link in my _Layout to bring me there and it also comes up as \\localhost\ABIAdmin (without the \Home). It's easy enough to require our users to provide the full url with home in it, but I need the link to also provide \Home in it. Here's the html for the Home link:
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Dashboard</a>
So now the question is how do I get \Home in the url from the link, and if possible on startup? Can this be addressed through IIS, or through my endpoints?
I have an ASP.NET Core 3.1 MVC application with Razor Pages which works fine when I deploy it to IIS if I do not use the Default Web Site, or if I run the application as an exe. All of my navigation works, and all of my CRUD operations work (I'm using Syncfusion's DataGrid). However, if I deploy to the Default Web Site I run into what seem to be routing issues. Please note the following:
I added a folder under c:\inetpub\wwwroot called ABIAdmin, which contains my core app.
Relevant code from Startup/ConfigureServices: services.AddControllersWithViews();
Relevant code from Startup/Configure:
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(
name: "Privacy",
pattern: "{controller=Privacy}/{action=Privacy}/{id?}");
});
I have about 80 controllers not including the Privacy and Home controllers. There is no routing information in any of the controllers.
I tried messing with adding virtual directories but that did not help.
When I navigate to the Campaigns page from the Privacy page it forms a proper URL, e.g., http://localhost:5000/ABIAdmin/Campaigns, where ABIAdmin is the name of the site under the Default Web Site. But if I navigate from the Home page I get a 404 error. The requested URL is formed as http://localhost:5000/Campaigns, but it is missing the "/ABIAdmin", and the physical path is wrong too: C:\inetpub\wwwroot\Campaigns. When I successfully navigate from the Privacy page, the data is retrieved, but if I try to perform an Add/Update/Delete operation it just hangs. I believe this is because whatever URL the grid is forming is wrong. I don't think this is the fault of the Syncfusion grid.
So the question is, why does the Default Web Site behave differently then a standalone web site in this situation? We will be deploying this to multiple customers, and some will want it under the Default Web Site.
Any help would be appreciated. :)
It's looks like I found an answer. I have 3 scenarios to handle, running from VS, running from a standalone website, and running from an application under the Default Web Site. I could not figure out a way to tell in my _Layout.cshtml whether it is running under the Default Web Site so I'm using a configuration parameter to tell me that. I can tell whether I'm running from VS by the name of the application pool (from VS it's "ABIAdminApp AppPool"). I can also tell if I'm coming from the Home page now (that was the missing link), by checking Context.Request.Path. Given that info I can conditionally add the name of my application, ABIAdminApp. Note in the original post I may have referred to this as ABIAdmin. I will consider making the name of the app a configuration parameter as well.
#{
#using ABIAdminApp.Classes;
#using Microsoft.Extensions.Configuration;
#inject IConfiguration Configuration;
string appPoolId = System.Environment.GetEnvironmentVariable("APP_POOL_ID");
string appName = "";
if (appPoolId != "ABIAdminApp AppPool" && Context.Request.Path.ToString() == "" && Extensions.UseDefaultWebsite(Configuration)) { appName = "ABIAdminApp" + "/"; } else { appName = ""; }
string anchor_template = "<a href='" + appName + "${URL}'>${FriendlyName}</a>";
}
So that solves my navigation problems. I also had to make changes for the Syncfusion grid updates. I had to conditionally add the app name and used ViewBag for that purpose with dynamic values for insertUrl, updateUrl, and removeUrl.
So in my Index method of the controller for Campaigns I had this code (preceded by logic to determine whether or not to add the app name):
ViewBag.InsertUrl = AppName + "/Campaigns/Insert";
ViewBag.UpdateUrl = AppName + "/Campaigns/Update";
ViewBag.RemoveUrl = AppName + "/Campaigns/Delete";
And this code in my view, Campaigns.cshtml:
<e-data-manager url="Campaigns/Campaigns" adaptor="UrlAdaptor" insertUrl="#ViewBag.InsertUrl" updateUrl="#ViewBag.UpdateUrl" removeUrl="#ViewBag.RemoveUrl"></e-data-manager>

Azure AD B2C ASP.NET redirect loop

We've implemented Azure AD B2C in Umbraco on the front end using Microsofts webapp sample https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
Most of the time this is generally working, but after a while everyone starts getting hit by a redirect loop. Restating the website then clears the issue.
It seems to be something causing the .AspNet.Cookies cookie to stop being set when the user is redirected back to the site with an id token.
Any ideas?
For the folks that will run into the same problem and find this question, I wanted to share what caused this in my case and how I resolved it.
The AD B2C App Registration expects to have a RedirectURI. I forgot to put signin-oidc
So changing:
https://localhost:5000
To
https://localhost:5000/signin-oidc
resolved my problem.
This is the default value - /signin-oidc - unless something else is explicitly set.
I had infinite loop issue at logout and it was because of missing support of Razor pages. The default Microsoft.Identity.Web.UI SignOut action uses /Account/SignedOut Razor page as callback url.
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
I added Razor support in my Asp.Net core web app and it fixed the issue.
services.AddRazorPages();
and
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
Thanks.
Please ensure that your Reply URL in your application registration matches your Redirect URI in the web.config. Try setting both of these to your main homepage URL to ensure that your app is registered properly. Also make sure that the Application ID and the Client ID are matching and the right tenant is set in your web config. This needs to be the onmicrosoft.com tenant. Also, ensure that your users have the right permissions for the application.
Please follow the instructions in my blog and video to ensure that these are set properly.
https://medium.com/#marilee.turscak/reply-urls-vs-postlogoutredirecturis-in-azure-active-directory-aad-20f57a03267b
https://www.youtube.com/watch?v=A9U1VGyztEM
You can also try deleting the application and republishing it. If none of these things work, it may actually be an issue with the platform itself.
enabled HTTPS only under TLS/SSL settings in web app .
For me, it was because I didn't have the scope defined in my b2c configuration settings, like this:
"Resources": {
"myApi": {
"ResourceUri": "https://localhost:44361",//"https://my.ui.com",
"ResourceScopes": [
"https://myapp.onmicrosoft.com/my-api/Admin.Read.Write" // this was wrong, which caused my looping
]
}
}
I was also getting a logout redirect loop. It would actually log out, but just get stuck in a loop. In my case, the redirect URL I had configured in Azure was fine (I had /signin-oidc).
I followed the guide on adding my own account controller action rather than using the built in 'MicrosoftIdentity/Account/SignOut' (while also adding the 'id_token' validation to secure the logout): https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application-options#secure-your-logout-redirect
My startup.cs code is per the documentation, my controller code looks like this (the documentation code is missing 'AuthenticationProperties' variable):
namespace Cosmos.WebPortal.Controllers;
[AllowAnonymous]
[Area("MicrosoftIdentity")]
[Route("[area]/[controller]/[action]")]
public class MyAccountController : Controller
{
[HttpGet("{scheme?}")]
public async Task<IActionResult> SignOutAsync([FromRoute] string scheme)
{
scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
var redirectUrl = Url.Content("~/");
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
//obtain the id_token
var idToken = await HttpContext.GetTokenAsync("id_token");
//send the id_token value to the authentication middleware
properties.Items["id_token_hint"] = idToken;
return SignOut(properties, CookieAuthenticationDefaults.AuthenticationScheme, scheme);
}
}
So my logout link is now to this controller instead e.g. 'MicrosoftIdentity/MyAccount/SignOut'
That seems to work fine, no infinite loop. A bit frustrating as I don't really understand the cause or difference, but it works.
For me, it was an expired secret/certificate in Azure B2C. It's important to look at the network log to see if any message, thankfully there was message telling me exactly where to look

Setting username in application insights

I am new to application insights and have set it up using no custom events and I'm using all the defaults. The application is build on MVC 5. In the ApplicationInsights.config there's a comment saying:
"When implementing custom user tracking in your application, remove this telemetry initializer to ensure that the number of users is accurately reported to Application Insights."
We have a page where you are required to login so the default user logging isn't saying that much and we would much rather have the username being the unique identifier. Based on the comment it seems like this should be some kind of common modification and therefor easy to modify. When trying to google on "custom user tracking" I do not find anything interesting which seems a bit odd...
So how do I link the user in Application Insights to my username instead of going on some cookie which seems to be the default behaviour?
To link the user to your custom username, you can create the following telemetry initializer:
public class RealUserIDTelemetryInitializer:ITelemetryInitializer
{
public void Initialize(Microsoft.ApplicationInsights.Channel.ITelemetry telemetry)
{
// Replace this with your custom logic
if (DateTime.Now.Ticks % 2 > 0)
{
telemetry.Context.User.Id = "Ron Weasley";
}
else
{
telemetry.Context.User.Id = "Hermione Granger";
}
}
}
Then register this telemetry initializer in AI.config.
<TelemetryInitializers>
....
<Add Type="MyApp.RealUserIDTelemetryInitializer, MyApp" />
</TelemetryInitializers>

grails spring security login page display

I'm using the spring security plugin in a Grails app. There are two reasons why the login page gets displayed
The user navigated to it directly
The user tried to access a page that is only available to logged-in users
In the case of (2) only, I want to display a message like "you attempted to access a page that requires login", but in the GSP code of the login page I can't find a way to distinguish between (1) and (2), is this possible?
When you get redirected, Spring Security stores a SavedRequest in the session under the SPRING_SECURITY_SAVED_REQUEST_KEY key, so you could check for the existence of that in auth.gsp:
<g:if test='${session.SPRING_SECURITY_SAVED_REQUEST_KEY}'>
// display "you attempted to access a page that requires login"
</g:if>
<g:else>
// direct access to login
</g:else>
You could change the url of of the various spring security configurations to point to a controller, and then have it branch based on info in the session. In a 1.3.7 project is did something like
security {
authenticationFailureUrl = '/logout/doLogout'
afterLogoutUrl = '/logout/doLogout'
}
then had
class LogoutController {
def doLogout = {
def wasHere = session.getAttribute('some-attribute-you-set')
if (wasHere) render view: 'requirelogin'
else render view: 'normallogin'
}
}

Problem with Remember Me Service in Spring Security

I'm trying to implement a "remember me" functionality in my website using Spring. The cookie and entry in the persistent_logins table are getting created correctly. Additionally, I can see that the correct user is being restored as the username is displayed at the top of the page.
However, once I try to access any information for this user when they return after they were "remembered", I get a NullPointerException. It looks as though the user isn't being set in the session again.
My applicationContext-security.xml contains the following:
<remember-me data-source-ref="dataSource" user-service-ref="userService"/>
...
<authentication-provider user-service-ref="userService" />
<jdbc-user-service id="userService" data-source-ref="dataSource"
role-prefix="ROLE_"
users-by-username-query="select email as username, password, 1 as ENABLED from user where email=?"
authorities-by-username-query="select user.id as id, upper(role.name) as authority from user, role, users_roles where users_roles.user_fk=id and users_roles.role_fk=role.name and user.email=?"/>
I thought it may have had something to do with users-by-username query but surely login wouldn't work correctly if this query was incorrect?
Any help on this would be greatly appreciated.
Thanks,
gearoid.
Can you please include the entire stack trace of the exception? I suspect that because you have not set the key attribute on the remember-me configuration that you specified above that the token is not being set on the SecurityContextHolder.
To see details of how Remember Me works you should take a look at the source for the RememberMeAuthenticationFilter. You can find that source here (directly):
http://grepcode.com/file/repo1.maven.org/maven2/org.springframework.security/spring-security-web/3.0.2.RELEASE/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java
RememberMeAuthenticationFilter is going to call in the RememberMeAuthenticationProvider as a result of:
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
Inside the authenticate method you can see that it will throw an exception if you do not specify a key:
if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication).getKeyHash()) {
throw new BadCredentialsException(messages.getMessage("RememberMeAuthenticationProvider.incorrectKey",
"The presented RememberMeAuthenticationToken does not contain the expected key"));
}
The key can literally be any string "your-company-name-{GUID}" or something like that. So then your remember-me would look more like this:
<remember-me key="your-company-name-rmkey-aWeFFTgxcv9u1XlkswUUiPolizxcwsqUmml" token-validity-seconds="3600" data-source-ref="dataSource"/>
Setting the token-validity is a really good idea which you should do.
Grant

Resources