Grails Spring Security: how can I allow the password null? - grails

I'm using Grails Spring Security Plugin. I have a requirement that for a kind of user, the password is not needed. So I override the constraints of password in default User domain:
class SecUser {
...
static constraints = {
username blank: false, unique: true
password blank: false, nullable: true
}
...
}
But this causes a lot of problems:
beforeInsert in User domain breaks because springSecurityService.encodePassword(password) couldn't accept a null value; then I override the beforeInsert:
def beforeInsert() {
if (someCondition) {
super.beforeInsert()
passwordChangeDate = new Date()
}
}
UserDetails class breaks because the constructor couldn't accept password as a null value, so I override the UserDetails:
import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.User.AuthorityComparator
import org.springframework.util.Assert
class ILUserDetails extends GrailsUser {
final String name
ILUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String name) {
//Override User
if (((username == null) || "".equals(username))) { // Allow null for password
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
this.id=id
this.name = name
}
private static SortedSet<GrantedAuthority> sortAuthorities(
Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<GrantedAuthority>(
new AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority,
"GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
}
Then I override the beforeInsert and the UserDetails, bug a strange bug happens:
java.lang.NoSuchMethodError: grails.plugin.springsecurity.userdetails.GrailsUser: method <init>()V not found
com.app.security.ILUserDetails.<init>(ILUserDetails.groovy)
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
java.lang.reflect.Constructor.newInstance(Constructor.java:526)
org.springsource.loaded.ri.ReflectiveInterceptor.jlrConstructorNewInstance(ReflectiveInterceptor.java:986)
org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:102)
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:57)
Now, I'm not sure letting the password be nullable is a good idea or not. Could someone give me some advice?

The previous ILUserDetails has errors because the private attribute is not accessible for subclass. So I rewrote the whole User class according to GrailsUser.java and User.java.
//Rewrite according to GrailsUser and User
//https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/core/userdetails/User.java
//https://github.com/grails-plugins/grails-spring-security-core/blob/master/src/java/grails/plugin/springsecurity/userdetails/GrailsUser.java
public class ILUser implements UserDetails, CredentialsContainer {
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
private static final long serialVersionUID = 1;
private final Object id;
private final String name;
// ~ Constructors
// ===================================================================================================
/**
* Construct the <code>User</code> with the details required by
* {#link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
*
* #param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
*/
public ILUser(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities, Object id, String name) {
if (((username == null) || "".equals(username))) { // Here is the key line
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
this.id = id;
this.name = name;
}
// ~ Methods
// ========================================================================================================
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isEnabled() {
return enabled;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void eraseCredentials() {
password = null;
}
private static SortedSet<GrantedAuthority> sortAuthorities(
Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<GrantedAuthority>(
new AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority,
"GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>,
Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
// Neither should ever be null as each entry is checked before adding it to
// the set.
// If the authority is null, it is a custom authority and should precede
// others.
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
/**
* Returns {#code true} if the supplied object is a {#code User} instance with the
* same {#code username} value.
* <p>
* In other words, the objects are equal if they have the same username, representing
* the same principal.
*/
#Override
public boolean equals(Object rhs) {
if (rhs instanceof ILUser) {
return username.equals(((ILUser) rhs).username);
}
return false;
}
/**
* Returns the hashcode of the {#code username}.
*/
#Override
public int hashCode() {
return username.hashCode();
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(": ");
sb.append("Username: ").append(this.username).append("; ");
sb.append("Password: [PROTECTED]; ");
sb.append("Enabled: ").append(this.enabled).append("; ");
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired)
.append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
if (!authorities.isEmpty()) {
sb.append("Granted Authorities: ");
boolean first = true;
for (GrantedAuthority auth : authorities) {
if (!first) {
sb.append(",");
}
first = false;
sb.append(auth);
}
}
else {
sb.append("Not granted any authorities");
}
return sb.toString();
}
/**
* Get the id.
* #return the id
*/
public Object getId() {
return id;
}
}

Related

Thymeleaf - th:checked works, th:field fails

Thymeleaf
Im trying to bind a variable called 'permitirAcesso' to thymeleaf. When I use th:checked, it shows the value correctly. When I use th:field, it doesnt bind. What should I do?
I investigated and th:text shows the correct value. Ive also tried changing the variable name, but it still doesnt work. Ive also tried to use a common html checkbox, it still doesnt bind.
Ive simplified the page and removed everything except the form and the malfunction persists. It works with th:checked but fails to bind with th:field.
Here's my code:
<head>
<meta charset="UTF-8"/>
</head>
<body>
<form id="formulario" th:action="#{/painel-do-administrador/usuarios/salvar}" th:object="${usuario}" method="POST" class="action-buttons-fixed">
<input type="checkbox" name="permitirAcesso" id="permitirAcesso" th:field="*{permitirAcesso}" />
<input type="checkbox" name="permitirAcesso" id="permitirAcesso" th:checked="*{permitirAcesso}" />
</form>
</body>
My Java Code:
#Entity
#Table(name = "users")
public class User implements UserDetails, Comparable<User> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank(message = "O nome não pode ser nulo")
#NotNull(message = "O nome não pode ser nulo")
#Column(name = "username", nullable = false, length = 512, unique = true)
private String username;
#Column(name = "nome_completo", updatable = true, nullable = false)
private String nomeCompleto;
#Column(name = "password", updatable = true, nullable = false)
private String password;
#Column(name = "ultimo_acesso")
private ZonedDateTime ultimoAcesso;
#Email(message = "Insira uma e-mail válido")
#Column(name = "email", updatable = true, nullable = false)
private String email;
#Column(name = "permitir_acesso")
private boolean permitirAcesso;
#Lob
#Column(name = "avatar", columnDefinition = "BLOB")
private byte[] avatar;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNomeCompleto() {
return nomeCompleto;
}
public void setNomeCompleto(String nomeCompleto) {
this.nomeCompleto = nomeCompleto;
}
#Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public ZonedDateTime getUltimoAcesso() {
return ultimoAcesso;
}
public void setUltimoAcesso(ZonedDateTime ultimoAcesso) {
this.ultimoAcesso = ultimoAcesso;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isPermitirAcesso() {
return permitirAcesso;
}
public String getPermitirAcessoString() {
if (permitirAcesso) {
return "Sim";
} else {
return "Não";
}
}
public void setPermitirAcesso(boolean permitirAcesso) {
this.permitirAcesso = permitirAcesso;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public void addRole(Role role) {
this.roles.add(role);
}
public boolean hasRole(Role role) {
Iterator<Role> iterator = this.roles.iterator();
while (iterator.hasNext()) {
if (role.equals(iterator.next())) {
return true;
}
}
return false;
}
public boolean hasRole(String roleName) {
for (Role role : this.roles) {
if (role.getName().equals(roleName)) {
return true;
}
}
return false;
}
#Override
#JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
}
#Override
public int compareTo(User o) {
return getEmail().compareToIgnoreCase(o.getEmail());
}
#Override
#JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
#Override
#JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
#Override
#JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
#Override
#JsonIgnore
public boolean isEnabled() {
return isPermitirAcesso();
}
}
Ok. This was a stupid mistake of mine. So what happened is that I had a Boolean Converter that converted Boolean to String. Removing the converter, fixed the issue.

Vaadin binding objects

I am trying to bind a textfield to an object. I've done some research and I have found this answer.
public class Person {
String name;
String surname;
Address address;
// assume getters and setters
}
public class Address {
String street;
// assume getter and setters
}
Then, you could bind the street address like this:
Binder<Person> binder = new Binder<>();
TextField streetAddressField = new TextField();
// bind using lambda expressions
binder.bind(streetAddressField,
person -> person.getAddress().getStreet(),
(person, street) -> person.getAddress().setStreet(street));
What value do I instantiate street as (in the last line of code)?
The above was the example I found. My code is as follows - I have a contact class:
#Entity
public class Contact {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String phoneNumber;
#ManyToOne (cascade = {CascadeType.ALL})
#JoinColumn(name="phoneType_typeId")
private PhoneType phoneType;
public Contact(){
}
public Contact(String firstName, String lastName, String phoneNumber, PhoneType type) {
this.firstName = firstName;
this.lastName = lastName;
this.phoneNumber = phoneNumber;
this.phoneType = type;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public PhoneType getPhoneType() {
return phoneType;
}
public void setPhoneType(PhoneType phoneType) {
this.phoneType = phoneType;
}
#Override
public String toString() {
return String.format("Contact[firstName='%s', lastName='%s', phoneNumber='%s', phoneType = '%s']",
firstName, lastName, phoneNumber, phoneType);
}
}
Then I have a phoneType class:
#Entity
#Table(name="phoneType")
public class PhoneType {
#Id
#GeneratedValue
#Column(name = "typeId")
private Long id;
private String type;
public PhoneType(String type){
this.type = type;
}
public PhoneType(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#Override
public String toString() {
return type;
}
}
Then in a Contact Editor I am trying to bind the phoneType to a textfield:
#SpringComponent
#UIScope
public class ContactEditor extends VerticalLayout {
private final ContactRepository repository;
private Contact contact;
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
TextField phoneNumber = new TextField("Phone number");
TextField phoneType = new TextField( "Phone type");
Button save = new Button("Save", VaadinIcons.CHECK);
Button cancel = new Button("Cancel");
Button delete = new Button("Delete", VaadinIcons.TRASH);
CssLayout actions = new CssLayout(save, cancel, delete);
Binder<Contact> binder = new Binder<>(Contact.class);
#Autowired
public ContactEditor(ContactRepository repository, Contact contact) {
this.repository = repository;
this.contact = contact;
String type = contact.getPhoneType().getType();
addComponents(firstName, lastName, phoneNumber, phoneType, actions);
// bind using naming convention
**binder.bind(phoneType, contact.getPhoneType().getType(), contact.getPhoneType().setType(type));**
binder.bindInstanceFields(this);
// Configure and style components
setSpacing(true);
actions.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
save.setStyleName(ValoTheme.BUTTON_PRIMARY);
save.setClickShortcut(ShortcutAction.KeyCode.ENTER);
// wire action buttons to save, delete and reset
save.addClickListener(e -> repository.save(contact));
delete.addClickListener(e -> repository.delete(contact));
cancel.addClickListener(e -> editContact(contact));
setVisible(false);
}
public interface ChangeHandler {
void onChange();
}
public final void editContact(Contact c) {
if (c == null) {
setVisible(false);
return;
}
final boolean persisted = c.getId() != null;
if (persisted) {
// Find fresh entity for editing
contact = repository.findById(c.getId()).get();
}
else {
contact = c;
}
cancel.setVisible(persisted);
// Bind customer properties to similarly named fields
// Could also use annotation or "manual binding" or programmatically
// moving values from fields to entities before saving
binder.setBean(contact);
setVisible(true);
// A hack to ensure the whole form is visible
save.focus();
// Select all text in firstName field automatically
firstName.selectAll();
}
public void setChangeHandler(ChangeHandler h) {
// ChangeHandler is notified when either save or delete
// is clicked
save.addClickListener(e -> h.onChange());
delete.addClickListener(e -> h.onChange());
}
}
The line enclosed in ** in Contact Editor (i.e. binder.bind(phoneType, contact.getPhoneType().getType(), contact.getPhoneType().setType(type))) is giving me an error - "no instance of type variable FIELDVALUE exist so that string conforms to ValueProvider .
The line
binder.bind(phoneType, contact.getPhoneType().getType(), contact.getPhoneType().setType(type));
does not compile because the method arguments do not match to any of the bind methods, and there is an illegal Java expression in the 3rd argument. According to your question, you have simply forgotten to use lambdas. Try:
binder.bind(phoneType, c -> c.getPhoneType().getType(), (c, t) -> c.getPhoneType().setType(t));
Have a look at the method signature:
public <FIELDVALUE> Binder.Binding<BEAN,FIELDVALUE> bind(HasValue<FIELDVALUE> field,
ValueProvider<BEAN,FIELDVALUE> getter,
Setter<BEAN,FIELDVALUE> setter)
It expects ValueProvider and Setter as 2nd and 3rd argument. These interfaces have only one method to be implemented, therefore you can use lambdas to pass them to bind.
I don't know if this is what you'r asking, but what I see as missing is that you haven't binded your binder to any bean.
You have created the binder, and you've told your textfield which property is binded to, but now you need to tell the binder which is his bean.
Something like:
Person yourPerson = new Person(); //or get person from database somehow
yourPerson.setAddress(new Address());
yourPerson.getAddress().setStreet("Road cool code, 404");
binder.setBean(yourPerson);
This should do the trick... if not, please explain better what you need. ;)

spring security for certain entity variables

Is it possible to secure update of certain entity properties using spring security.? for example if I have a user entity , I want ROLE_USER to be able to modify/update all the properties of user except active column which would be updatable by ROLE_ADMIN.
I haven't found any solution strictly provided by Spring Security Yet. However I have achieved what I wanted as follows by using custom annotation #SecureUpdate() on entity variables:-
Following is my paging and sorting repository:-
#Transactional("jpaTXManager")
public interface ScreenRepo extends PagingAndSortingRepository<Screen, Integer>{
#Override
#PreAuthorize("#patchSecurityService.canUpdate(#screen)")
Screen save(#Param("screen")Screen screen);
}
PatchSecurityService.java
#Service("patchSecurityService")
public class PatchSecurityService {
public boolean canUpdate(Object obj){
List<GrantedAuthority> authorities =
(List<GrantedAuthority>) SecurityContextHolder
.getContext()
.getAuthentication()
.getAuthorities();
if (obj instanceof OEntity){
OEntity oEntity = (OEntity) obj;
return oEntity.canUpdate(authorities);
}else{
return true;
}
}
}
OEntity.java
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean canUpdate(List<GrantedAuthority> authorities){
for (Field field : this.getClass().getDeclaredFields()){
SecureUpdate secureUpdate = field.getAnnotation(SecureUpdate.class);
if (secureUpdate != null){
try{
field.setAccessible(true);
Object persistedField = field.get(this);
Object originalField = field.get(originalObj);
String[] allowedRoles = secureUpdate.value();
if (!persistedField.equals(originalField)){
boolean canUpdate = false;
for (String role : allowedRoles){
for (GrantedAuthority authority : authorities){
if (authority.getAuthority().equalsIgnoreCase(role)){
return true;
}
}
}
return false;
}
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
return true;
}
}
#SecureUpdate
#Documented
#Target(FIELD)
#Retention(RUNTIME)
public #interface SecureUpdate {
String[] value();
}
and finally entity class (Screen.class)
#Entity
#Table(name="screen",schema="public")
#JsonIgnoreProperties(ignoreUnknown=true)
public class Screen extends OEntity<Screen>{
Integer screenId;
String screenName;
#SecureUpdate({"ROLE_CLIENT"})
String address;
ScreenType screenType;
#SecureUpdate({"ROLE_ADMIN"})
ScreenSize screenSize;
BigDecimal latitude;
BigDecimal longitude;
Boolean active;
AppUser appUser;
.......Constructor, Getters and Setters...
}

Null Pointer Exception using PlainUsername

When I Edit the user all are saving but when I change the password it is getting the error.Please help me.
NullPointerException occurred when processing request: [POST] /openbrm /user/save
Stacktrace follows:
java.lang.NullPointerException
at com.sapienter.jbilling.client.authentication.CompanyUserDetails.getPlainUsername(CompanyUserDetails.java:84)
at com.sapienter.jbilling.client.authentication.JBillingPasswordEncoder.isPasswordValid(JBillingPasswordEncoder.java:75)
at com.sapienter.jbilling.client.user.UserHelper.bindPassword(UserHelper.groovy:155)
at jbilling.UserController.save(UserController.groovy:304)
at grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.java:53)
at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:82)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
UserController.groovy
def save () {
UserWS user = new UserWS()
user.mainRoleId= Constants.TYPE_ROOT
UserHelper.bindUser(user, params)
def contacts = []
def userId= params['user']['userId'] as Integer
log.debug "Save called for user ${userId}"
def oldUser = userId ? webServicesSession.getUserWS(userId) : null
def company_id = session['company_id']
def company = CompanyDTO.createCriteria().get {
eq("id", company_id)
fetchMode('contactFieldTypes', FM.JOIN)
}
if ( !oldUser || SpringSecurityUtils.ifAllGranted('ROLE_SUPER_USER') || SpringSecurityUtils.ifAllGranted('MY_ACCOUNT_162') ) {
UserHelper.bindUser(user, params)
UserHelper.bindContacts(user, contacts, company, params)
} else {
user= oldUser
contacts= userId ? webServicesSession.getUserContactsWS(userId) : null
}
if ( !oldUser || SpringSecurityUtils.ifAllGranted('ROLE_SUPER_USER') || SpringSecurityUtils.ifAllGranted('MY_ACCOUNT_161') ) {
UserHelper.bindPassword(user, oldUser, params, flash)
} else {
user.password= null
}
UserDTO loggedInUser = UserDTO.get(springSecurityService.principal.id)
if (flash.error) {
user = new UserWS()
UserHelper.bindUser(user, params)
contacts = []
UserHelper.bindContacts(user, contacts, company, params)
render view: 'edit', model: [user: user, contacts: contacts, company: company, loggedInUser: loggedInUser, roles: loadRoles()]
return
}
try {
if (!oldUser) {
log.debug("creating user ${user}")
user.userId = webServicesSession.createUser(user)
flash.message = 'user.created'
flash.args = [user.userId as String]
} else {
log.debug("saving changes to user ${user.userId}")
webServicesSession.updateUser(user)
flash.message = 'user.updated'
flash.args = [user.userId as String]
}
// save secondary contacts
if (user.userId) {
contacts.each {
webServicesSession.updateUserContact(user.userId, it);
}
}
} catch (SessionInternalError e) {
flash.clear()
viewUtils.resolveException(flash, session.locale, e)
contacts = userId ? webServicesSession.getUserContactsWS(userId) : null
if(!contacts && !userId){
contacts = [user.getContact()]
}
render view: 'edit', model: [user: user, contacts: contacts, company: company, loggedInUser: loggedInUser, roles: loadRoles()]
return
}
if ( SpringSecurityUtils.ifAnyGranted("MENU_99") || SpringSecurityUtils.ifAnyGranted("ROLE_SUPER_USER") ) {
chain action: 'list', params: [id: user.userId]
} else {
chain action: 'edit', params: [id: user.userId]
}
}
In UserHelper.groovy it is getting the error at this method
static def bindPassword(UserWS newUser, UserWS oldUser, GrailsParameterMap params, flash) {
if (oldUser) {
// validate that the entered confirmation password matches the users existing password
if (params.newPassword) {
//read old password directly from DB. API does not reveal password hashes
def oldPassword = UserDTO.get(oldUser.userId).password
PasswordEncoder passwordEncoder = Context.getBean(Context.Name.PASSWORD_ENCODER)
//fake user details so we can verify the customers password
//should we move this to the server side validation?
CompanyUserDetails userDetails = new CompanyUserDetails(
oldUser.getUserName(), oldPassword, true, true, true, true,
Collections.EMPTY_LIST, null,null,oldUser.getUserId(), oldUser.getMainRoleId(), oldUser.getEntityId(),
oldUser.getCurrencyId(), oldUser.getLanguageId()
)
if (!passwordEncoder.isPasswordValid(oldPassword, params.oldPassword, userDetails)) {
flash.error = 'current.password.doesnt.match.existing'
return
}
} else {
newUser.setPassword(null)
}
}
// verify passwords only when new password is present
if (params.newPassword) {
if (params.newPassword == params.verifiedPassword) {
if (params.newPassword)
newUser.setPassword(params.newPassword)
} else {
flash.error = 'passwords.dont.match'
}
} else {
newUser.setPassword(null)
}
}
My CompanayUserDetails.java
package com.sapienter.jbilling.client.authentication;
import com.sapienter.jbilling.server.user.db.UserDTO;
import org.springframework.security.core.GrantedAuthority;
import grails.plugin.springsecurity.userdetails.GrailsUser;
import java.util.Collection;
import java.util.Locale;
public class CompanyUserDetails extends GrailsUser {
private final UserDTO user;
private final Locale locale;
private final Integer mainRoleId;
private final Integer companyId;
private final Integer currencyId;
private final Integer languageId;
public CompanyUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
UserDTO user, Locale locale,
Integer id, Integer mainRoleId, Integer companyId, Integer currencyId, Integer languageId) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id);
this.user = user;
this.locale = locale;
this.mainRoleId = mainRoleId;
this.companyId = companyId;
this.currencyId = currencyId;
this.languageId = languageId;
}
public UserDTO getUser() {
return user;
}
public String getPlainUsername() {
return user.getUserName();
}
public Locale getLocale() {
return locale;
}
public Integer getMainRoleId() {
return mainRoleId;
}
public Integer getUserId() {
return (Integer) getId();
}
public Integer getCompanyId() {
return companyId;
}
public Integer getCurrencyId() {
return currencyId;
}
public Integer getLanguageId() {
return languageId;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("CompanyUserDetails");
sb.append("{id=").append(getId());
sb.append(", username=").append("'").append(getUsername()).append("'");
sb.append(", mainRoleId=").append(getMainRoleId());
sb.append(", companyId=").append(getCompanyId());
sb.append(", currencyId=").append(getCurrencyId());
sb.append(", languageId=").append(getLanguageId());
sb.append(", enabled=").append(isEnabled());
sb.append(", accountExpired=").append(!isAccountNonExpired());
sb.append(", credentialsExpired=").append(!isCredentialsNonExpired());
sb.append(", accountLocked=").append(!isAccountNonLocked());
sb.append('}');
return sb.toString();
}
}
Well you are passing null into the constructor for UserDTO
see
for
Collection<GrantedAuthority> authorities, UserDTO user, Locale locale,
you are passing
Collections.EMPTY_LIST, null,null
so of course getPlainUsername will fail
In your call to new CompanyUserDetails
CompanyUserDetails userDetails = new CompanyUserDetails(
oldUser.getUserName(), oldPassword, true, true, true, true,
Collections.EMPTY_LIST, null, <--- param $8 is null
And the definition
public CompanyUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
UserDTO user, <--- param #8
And finally the NPE In your call to getPlainUsername
return user.getUserName();
NPE, can not invoke method on null user object.
So to understand your problem you really need to understand error codes:
java.lang.NullPointerException
at com.sapienter.jbilling.client.authentication.CompanyUserDetails.getPlainUsername(CompanyUserDetails.java:84)
According to my editor line 84 was
sb.append(", languageId=").append(getLanguageId());
I would suggest as a test set all these to a value
private final Integer mainRoleId=0;
private final Integer companyId=0;
private final Integer currencyId=0;
private final Integer languageId=0;
then change
this.mainRoleId = mainRoleId;
this.companyId = companyId;
this.currencyId = currencyId;
this.languageId = languageId
to
if (mainRoleId) { this.mainRoleId = mainRoleId;}
if (companyId) { this.companyId = companyId; }
if (currencyId) { this.currencyId = currencyId; }
if (languageId ) { this.languageId = languageId }
bad coding causes bad problems

Restricting dropwizard admin page

How to authenticate Dropwizard admin portal, so as to restrict normal users from accessing it?
Please help
In your config, you can set adminUsername and adminPassword under http like so:
http:
adminUsername: user1234
adminPassword: pass5678
For DW 0.7 my approach would be:
public class AdminConstraintSecurityHandler extends ConstraintSecurityHandler {
private static final String ADMIN_ROLE = "admin";
public AdminConstraintSecurityHandler(final String userName, final String password) {
final Constraint constraint = new Constraint(Constraint.__BASIC_AUTH, ADMIN_ROLE);
constraint.setAuthenticate(true);
constraint.setRoles(new String[]{ADMIN_ROLE});
final ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");
setAuthenticator(new BasicAuthenticator());
addConstraintMapping(cm);
setLoginService(new AdminMappedLoginService(userName, password, ADMIN_ROLE));
}
}
public class AdminMappedLoginService extends MappedLoginService {
public AdminMappedLoginService(final String userName, final String password, final String role) {
putUser(userName, new Password(password), new String[]{role});
}
#Override
public String getName() {
return "Hello";
}
#Override
protected UserIdentity loadUser(final String username) {
return null;
}
#Override
protected void loadUsers() throws IOException {
}
}
and using them in the way:
environment.admin().setSecurityHandler(new AdminConstraintSecurityHandler(...))
Newer Jetty versions do not have MappedLoginService, so #Kamil's answer no longer works. I have modified their answer to get it working as of Dropwizard 1.2.2:
public class AdminConstraintSecurityHandler extends ConstraintSecurityHandler {
private static final String ADMIN_ROLE = "admin";
public AdminConstraintSecurityHandler(final String userName, final String password) {
final Constraint constraint = new Constraint(Constraint.__BASIC_AUTH, ADMIN_ROLE);
constraint.setAuthenticate(true);
constraint.setRoles(new String[]{ADMIN_ROLE});
final ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");
setAuthenticator(new BasicAuthenticator());
addConstraintMapping(cm);
setLoginService(new AdminLoginService(userName, password));
}
public class AdminLoginService extends AbstractLoginService {
private final UserPrincipal adminPrincipal;
private final String adminUserName;
public AdminLoginService(final String userName, final String password) {
this.adminUserName = Objects.requireNonNull(userName);
this.adminPrincipal = new UserPrincipal(userName, new Password(Objects.requireNonNull(password)));
}
#Override
protected String[] loadRoleInfo(final UserPrincipal principal) {
if (adminUserName.equals(principal.getName())) {
return new String[]{ADMIN_ROLE};
}
return new String[0];
}
#Override
protected UserPrincipal loadUserInfo(final String userName) {
return adminUserName.equals(userName) ? adminPrincipal : null;
}
}
}

Resources