grails spring security CAS: cannot get login to work with non-local user - spring

My grails 2.5.0 app uses Spring Security with the ldap and cas plugins. I trust this CAS server, so I need to allow users with no local (User, Role, UserRole tables) records to authenticate.
If I have a local user, everything works great. If I don't have a local user, then I get the CAS re-direct error followed by "Sorry, we were not able to find a user with that username and password." I read that the GormUserDetailsService supplied with CAS doesn't work for non-local users, so I've written my own MyUserDetailsService that implements GrailsUserDetailsService, and I've registered it in ./conf/spring/resources.groovy:
beans = {
userDetailsService(edu.uga.reg.MyUserDetailsService)
}
Unfortunately, this doesn't solve the problem: it works with local users, but gives the same re-direct+user not found error if the user isn't in my local database. Here's MyUserDetailService code:
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.userdetails.GrailsUser
import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService
import grails.transaction.Transactional
import org.springframework.security.core.authority.GrantedAuthorityImpl
import edu.uga.reg.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException
class MyUserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least
* one role, so we give a user with no granted roles this one which gets
* past that restriction but doesn't grant anything.
*/
static final List NO_ROLES =
[new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles)
throws UsernameNotFoundException {
return loadUserByUsername(username)
}
UserDetails loadUserByUsername(String username) {
def user = User.findByUsername(username)
// No local user in my db: create one from this username
if (!user)
return new GrailsUser(username, '', true, true, true, NO_ROLES, 999)
def authorities = user.authorities.collect {
new GrantedAuthorityImpl(it.authority)
}
return new GrailsUser(user.username, user.password,
user.enabled, !user.accountExpired, !user.passwordExpired,
!user.accountLocked, authorities ?: NO_ROLES, user.id)
}
}
Here are my cas settings in Config.groovy:
grails.plugin.springsecurity.providerNames = ['casAuthenticationProvider']
grails.plugin.springsecurity.useCAS = true
grails.plugin.springsecurity.cas.active = true
grails.plugin.springsecurity.cas.loginUri = '/login'
grails.plugin.springsecurity.cas.serverUrlPrefix = 'https://cas.dev.server/cas'
grails.plugin.springsecurity.cas.serviceUrl = 'https://apps-dev.server:8743/CASIS/j_spring_cas_security_check'
grails.plugin.springsecurity.logout.afterLogoutUrl = 'https://cas.dev.server/cas/logout?url=http://apps-dev.server:8743/CASIS/'
//default cas settings
grails.plugin.springsecurity.cas.sendRenew = false
grails.plugin.springsecurity.cas.key = 'grails-spring-security-cas'
grails.plugin.springsecurity.cas.artifactParameter = 'ticket'
grails.plugin.springsecurity.cas.serviceParameter = 'service'
grails.plugin.springsecurity.cas.filterProcessesUrl = '/j_spring_cas_security_check'
grails.plugin.springsecurity.cas.useSingleSignout = true
grails.plugin.springsecurity.cas.serverUrlEncoding = 'UTF-8'
grails.plugin.springsecurity.cas.proxyCallbackUrl = null
grails.plugin.springsecurity.cas.proxyReceptorUrl = null
Again, CAS works fine if the user is in my local db, but fails otherwise. What am I missing? Thanks.

The answer to my problem is trivial. It's always a good idea to pass the correct number of arguments to the constructor. Instead of:
return new GrailsUser(username, '', true, true, true, NO_ROLES, 999)
I simply needed:
return new GrailsUser(username, '', true, true, true, true, NO_ROLES, 999)
And my user is setup as the principal!

Related

Override default Identity Server 4 Client

I created a .NET Core 3.0 Angular project with Identity Server. I want to add claims and roles to my app.
My Identity Server is mostly out of the box with some simple route changes:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = "/auth/login";
options.UserInteraction.LogoutUrl = "/auth/logout";
})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
I currently add my a simple policy in startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy =>
{
policy.RequireRole("Admin");
policy.RequireClaim("CreateUser");
});
});
At the bottom of my Configure() method I call this method to add roles:
private async Task CreateUserRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
IdentityResult adminRoleResult;
//Adding Admin Role
var adminRoleCheck = await RoleManager.RoleExistsAsync("Admin");
if (!adminRoleCheck)
{
//create the roles and seed them to the database
adminRoleResult = await RoleManager.CreateAsync(new IdentityRole("Admin"));
await RoleManager.AddClaimAsync(new IdentityRole("Admin"), new Claim("CreateUser", "Create User"));
}
}
In my register.cshtml.cs I've successfully set a role and a claim via:
var roleResult = await _userManager.AddToRoleAsync(user, "Admin");
var claimResult = await _userManager.AddClaimAsync(user, new Claim("CreateUser", "Create User"));
I've confirmed that the new user has that claim and role.
The client userinfo call returns correctly but when I look at the id_token I dont have those claims:
{
"nbf": 1574787988,
"exp": 1574788288,
"iss": "https://localhost:5001",
"aud": "MyAppSpa",
"iat": 1574787988,
"at_hash": "ndzldxAE3EiVzI4PeThNPQ",
"s_hash": "dIqJXx372XhOESn1XYH36A",
"sid": "VQLp--MHdoOoxXiVASWZ0g",
"sub": "4a0450dd-fe4f-4b3d-ac12-3f70876183e1",
"auth_time": 1574787983,
"idp": "local",
"amr": [
"pwd"
]
}
According to oidc-client-js is not getting claims correctly from Identity Server 4, I should just need to include AlwaysIncludeUserClaimsInIdToken = true to the client configuration.
However, the template doesnt have a configuration. Its all under the hood.
Questions:
1) Will adding AlwaysIncludeUserClaimsInIdToken = true to the client fix my issue?
2) How do I add it given my current configuration?
Concern about the size of ID Token , by default the claims won't include in ID token .You can get the claims from userinfo endpoint with ID token(read from user.profile) . That is by design .
The new .net core 3.0 angular authentication template just configures IdentityServer to use template supported configuration , but the configuration is not fully customize compare to Identity server provided configuration ,such as AlwaysIncludeUserClaimsInIdToken . So the workaround is not use ApiAuthorization service, the full power of IdentityServer is still available to customize authentication to suit your needs.

Ldap template authentication with user group

I stuck with spring ldaptemplate authentication method returning zero count while using the group string, the string like below.
CN=Jirra-Acdolite-DG,OU=Jira Security Group,OU=Apps Security Group,OU=Security Groups,OU=Global,OU=BT,DC=barcadero,DC=com
We are trying with ldap user group with following code.
try {
LdapContextSource ctxSrc = new LdapContextSource();
ctxSrc.setUrl(url);
// ctxSrc.setBase(base);
ctxSrc.setUserDn(ManagerDn);
ctxSrc.setPassword(ManagerPassword);
ctxSrc.setReferral("follow");
ctxSrc.afterPropertiesSet();
LdapTemplate ldapTemplate = new LdapTemplate(ctxSrc);
System.out.println("50");
ldapTemplate.afterPropertiesSet();
AndFilter andfilter = new AndFilter().and(new EqualsFilter("objectCategory", "person"))
.and(new EqualsFilter("objectClass", "user")).and(new EqualsFilter(SearchAttributes, userDn))
.and(new EqualsFilter("memberOf:1.2.840.113556.1.4.1941:",
"CN=Jirra-Acdolite-DG,OU=Jira Security Group,OU=Apps Security Group,OU=Security Groups,OU=Global,OU=BT,DC=barcadero,DC=com
"));
System.out.println(andfilter);
if (!ldapTemplate.authenticate(base, andfilter.encode(), password, new AuthenticationErrorCallback() {
public void execute(Exception e) {
System.out.println("exception");
}
})) {
System.out.println("False\n");
} else {
System.out.println("Success");
}
But we always getting the False value along with Group string. If any help on same thanks.
THe error message showing like below.
Feb 15, 2018 12:32:52 AM org.springframework.ldap.core.LdapTemplate authenticate
INFO: No results found for search, base:
CN=Jirra-Acdolite-DG,OU=Jira Security Group,OU=Apps Security Group,OU=Security Groups,OU=Global,OU=BT,DC=barcadero,DC=com
if you're trying to authenticate a user under a specific group , try to get all users under that group and make a search in it ( i done it like that)
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("memberOf:1.2.840.113556.1.4.1941:", groupDN));
filter.and(new EqualsFilter("objectClass", "user"));
return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), new ContractAttributeMapperJSON());
}

Secured users created in grails integration test are unauthorized but bootstrapped ones are

I'm using Grails Spring Security Core and the Grails Spring Security REST plugin and I'm just starting to get things set up. I initialized the plugins with a User class and an Authority class (defaults) and went to write an integration test, following a guide I found on the Grails website.
It said to put the following in an integration test:
def "test a user with the role ROLE_BOSS is able to access /api/announcements url"() {
when: 'login with the sherlock'
RestBuilder rest = new RestBuilder()
def resp = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = 'sherlock'
password = 'elementary'
}
}
then:
resp.status == 200
resp.json.roles.find { it == 'ROLE_BOSS' }
}
I went ahead and did something similar and it worked with a bootstrapped User, but when I tried to do the exact same test with a User created in the test method itself, it would fail with a 401 HTTP response code.
The code I'm trying to run:
void "check get access token"() {
given:
RestBuilder rest = new RestBuilder()
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
assert User.count == 2
when:
def resp = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = "securitySpecTestUserName"
password = "securitySpecTestPassword"
}
}
then:
resp.status == 200
}
Note that the User.count == 2 assertion passes because there is one User in Bootstrap.groovy and the one create in the test method.
Why does this work and pass with the bootstrapped User without any issues at all but not the one created in the method? Is there a way I can write this integration test so that I can test the /api/login endpoint included in the grails-spring-security-rest plugin in this way?
The User you create in the given section is in a transaction that has not been committed. When you make the REST call, the api/login controller will be run in a new transaction that cannot see your un-committed User.
A few options (there are others)...
Create User in BootStrap.groovy
def init = { servletContext ->
environments {
test {
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
}
}
}
Make REST calls to create the User - assuming you have such functionality
Create User in setup
#Integration
#Rollback
class UserIntSpec extends Specification {
def setup() {
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
}
void "check get access token"() {
given:
RestBuilder rest = new RestBuilder()
when:
def response = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = "securitySpecTestUserName"
password = "securitySpecTestPassword"
}
}
then:
response.status == HttpServletResponse.SC_OK
when:
def token = response.json.access_token
then:
token
}
}
Note: In Grails >= 3.0, setup() is run in a separate transaction and persisted (why it solves your problem) which is not rolled back. Any data will need to be cleaned up manually.
I suggest you read the grails documentation on testing: Integration Testing

Grails Spring Security Rest - 403 Forbidden

So I just started learning Grails, and I am trying incorporate the Spring Security REST plugin into my app, the plugin is installed in addition to spring security core which is working. In my REST client, when I hit "api/login" I am able to get an access token and it says I have the role of "ROLE_ADMIN", but then when I try to hit something using that, I keep getting a 403 Forbidden. In Postman, the REST client I am using, I have my Authorization header with "Bearer {key}", with my url of "http://localhost:8080/test/api/secret" and it gives the 403 error. I am trying to setup the log4j logging to see any other issues, but does anyone know what I should look into, any help would be appreciated. I provided my classes below if that helps, I generally used default values for everything such as the UrlMappings.
RandomController.groovy
package test
import grails.plugin.springsecurity.annotation.Secured
#Secured(['IS_AUTHENTICATED_ANONYMOUSLY'])
class MyController {
#Secured(['ROLE_ADMIN'])
def secret() {
render "You have ACCESS!!!"
}
}
Bootstrap.groovy
class BootStrap {
def init = { servletContext ->
def adminRole = new SecRole(authority: 'ROLE_ADMIN').save(flush: true)
def testUser = new SecUser(username: 'bob', password: 'test')
testUser.save(flush: true)
SecUserSecRole.create testUser, adminRole, true
}
def destroy = {
}
}
UrlMappings.groovy
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/api/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here
} }
"/"(view:"/index")
"500"(view:'/error')
}
}
For what I can see from the code you posted, if you invoke url http://localhost:8080/test/api/secret, it should execute default action (maybe index) in SecretController but the controller you posted is called MyController.
To investigate further, you should enable more verbose logging using log4j configuration as suggested in the doc http://alvarosanchez.github.io/grails-spring-security-rest/1.5.1/docs/guide/debugging.html

Sorry, we were not able to find a user with that username and password

I installed the Spring Security core plug-in 1.2.7.3 on Grails 2.1.1, ran the s2-quickstart command, and then initialized the initial user and roles in the bootstrap.groovy, but I still cannot login. Text of the relevant piece of BootStrap.groovy follows:
if (SecRole.count == 0) {
def fUserRole = SecRole.findByAuthority('ROLE_FlowUser') ?: new SecRole(authority: 'ROLE_FlowUser').save(failOnError: true, flush: true)
def fAdminRole = SecRole.findByAuthority('ROLE_FlowAdmin') ?: new SecRole(authority: 'ROLE_FlowAdmin').save(failOnError: true, flush: true)
def bf = SecUser.findByUsername('bill') ?: new SecUser(
username: 'bill',
password: 'eagle',
firstName: 'bill',
lastName: 'fly',
email: 'bill.fly#baylorhealth.edu',
accountExpired: false,
accountLocked: false,
passwordExpired: false,
enabled: true
).save(failOnError: true, flush: true)
if (!bf.authorities.contains(fAdminRole)) {
SecUserSecRole.create bf, fAdminRole, true
}
if (!bf.authorities.contains(fUserRole)) {
SecUserSecRole.create bf, fUserRole, true
}
}
I am not encrypting the password in bootstrap, as seems to be the answer to most of the questions of this type. All four records are getting written to the database tables, but of course, I cannot tell if the password is encrypted correctly. My initial controller has the following annotation ahead of the class statement:
#Secured(['IS_AUTHENTICATED_FULLY'])
Also, I added the following to the config.groovy:
// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'cocktail.SecUser'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'cocktail.SecUserSecRole'
grails.plugins.springsecurity.authority.className = 'cocktail.SecRole'
grails.plugins.springsecurity.password.algorithm = 'SHA-256'
Your password may be encoded two times (problem may occure if you are using multi datasources).
Try this :
class User {
...
transient bEncoded = false
...
protected void encodePassword() {
if (!bEncoded ) {
password = springSecurityService.encodePassword(password);
bEncoded = true;
}
}
}
My guess is the authorities.contains check is failing because of missing hashCode and equals methods in your role class. But if there are no roles (your 1st check) then the user wouldn't have any granted, so you can just remove those checks:
SecUserSecRole.create bf, fAdminRole, true
SecUserSecRole.create bf, fUserRole, true
If that doesn't fix it, it's most likely a password encoding issue - add debug logging for Spring Security and it should show you why it's failing; add debug 'org.springframework.security' in your log4j block in Config.groovy
p.s. if (SecRole.count == 0) { should be if (SecRole.count() == 0) { or just if (!SecRole.count()) {

Resources