spring data ldap: LdapRespository - how to find user by DN? - spring-data

I might not understand how the spring data ldap LdapRepository works but I'm confused because, I don't know how to find an LDAP user by DN.
For example, a user in my directory looks like:
dn: uid=nicolas,ou=mycompany,dc=com
objectclass: inetOrgPerson
objectclass: organizationalPerson
objectclass: person
objectclass: top
cn: nicolas
sn: nicolas
uid: nicolas
userPassword: $2a$12$qIaTIG3UVm0hfKRWbwO5EueXG.omG7FL0XmuVxlQ8UuJrozX8Tlk2
In a Spring Boot REST controller I get the current authenticated user like this:
Map principal = (Map)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String dn = principal.get("dn");
Here the "dn" string is "uid=nicolas,ou=people,dc=mycompany,dc=com"
The user class is as follows:
#Entry(base = "ou=people", objectClasses = {"top", "person", "organizationalPerson", "inetOrgPerson", "simpleSecurityObject"})
public class RdfLdapUser
{
#Id
private Name id;
#Attribute(name = "uid")
private String uid;
#Attribute(name = "cn")
private String cn;
#Attribute(name = "sn")
private String sn;
#Attribute(name = "dn")
private String dn;
}
And the repository:
#Repository
public interface RdfLdapRepository extends LdapRepository<RdfLdapUser>
{
public Optional<RdfLdapUser> findByUid (String uid);
public Optional<RdfLdapUser> findByDn (String dn);
}
Trying to find an user by its DN which is "uid=nicolas,ou=people,dc=mycompany,dc=com" raises the following exception:
java.lang.NullPointerException: null
at java.util.Hashtable.put(Hashtable.java:460)
at org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy.setupEnvironment(SimpleDirContextAuthenticationStrategy.java:42)
at org.springframework.ldap.core.support.AbstractContextSource.setupAuthenticatedEnvironment(AbstractContextSource.java:194)
at org.springframework.ldap.core.support.AbstractContextSource.getAuthenticatedEnv(AbstractContextSource.java:582)
at org.springframework.ldap.core.support.AbstractContextSource.doGetContext(AbstractContextSource.java:134)
at org.springframework.ldap.core.support.AbstractContextSource.getReadOnlyContext(AbstractContextSource.java:158)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:357)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:309)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:642)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:578)
at org.springframework.ldap.core.LdapTemplate.find(LdapTemplate.java:1840)
at org.springframework.ldap.core.LdapTemplate.find(LdapTemplate.java:1861)
at org.springframework.ldap.core.LdapTemplate.findOne(LdapTemplate.java:1869)
What am I doing wrong here ?
The other thing is that there is an annotation #DnAttribute which I don't understand the meaning. The documentation says:
Indicates that a field is to be automatically populated to/from the distinguished name of an entry. Fields annotated with this annotation will be automatically populated with values from the distinguished names of found entries. For automatic calculation of the DN of an entry to work, the index() value must be specified on all DnAttribute annotations in that class, and these attribute values, prepended with the Entry.base() value will be used to figure out the distinguished name of entries to create and update.
I simply don't get it. I've seen examples like that:
#DnAttribute(value="uid", index=0)
private String uid;
but I'm not sure what this is supposed to mean. It's saying that the the DN it's the uid index 0. For me, the DN is the DN and the uid is the uid and I dont understand what does it mean that the DN is the uid. Could someone please clarify ?
Many thanks in advance.
Kind regards,
Nicolas

I am here after one year and hope I can help yet. I would like to explain how #DnAttribute works at the beginning.
When you will try to save an entity looks like in the picture, you need to set attributes like in every other case, despite the #DnAttribute are #Transient, so they are not mirrored in the LDAP, #DnAttribute causes composing DN. Base DN is set into LdapContextSource.
For example when Base DN = "DC=company,DC=com", then you set the DN attributes into entity:
firstLevelOu = "firstLevelOu",
company = "ourCompany",
secondLevelOu = "secondLevelOu",
name = "newGroup"
DN of the record stored in the LDAP will look like: "CN=newGroup,OU=secondLevelOu,OU=ourCompany,OU=firstLevelOu,DC=company,DC=com"
When you want to get this group from LDAP, you only need LdapRepository, method findById():
You only need pass into method relative DN, base DN is set implicitly, so relative DN is CN=newGroup,OU=secondLevelOu,OU=ourCompany,OU=firstLevelOu
EDIT
LdapAdapterUtils.createLdapName:
import org.springframework.ldap.support.LdapUtils;
public final class LdapAdapterUtils {
public static LdapName createLdapName(String relativeDN) throws BadRequestAppException {
LdapName createdName;
try {
createdName = LdapUtils.newLdapName(relativeDN);
} catch (InvalidNameException e) {
throw new BadRequestAppException("Relative DN: " + relativeDN + "is not valid DN");
}
return createdName;
}
}

Related

Retrieving checkbox input (Groovy Html MarkupBuilder + SpringBoot)

I'm a newbie in Groovy and SpringBoot, but have to use these technologies in my work.
I have a User and Role classes, they don't have any relations inside classes. In Get-action in Controller I pass User and List of all possible roles to the form.
Controller class:
#GetMapping
public String edit(Model model){
model.addAttribute("user", new User());
model.addAttribute("roles", roleRepository.findAll());
return "edit";
}
My edit.tpl file:
...
form(..., method:'post'){
label(for:'username', 'Username: ')
input(type:'text', id:'username', name:'username', value:user.username)
label("Add roles: ")br()
roles.each {role ->
label(for:role.name, role.name)
input(type:'checkbox', id:role.name, name:role.name, value:role.name)
br()
}
input(type:'hidden', id:'_method', name:'_method', value:'put')
input(type:'submit', class:'btn btn-info', value:'Save')
}
I want to retrieve all the checked Roles.
Like that:
role_1 unchecked
role_2 checked
role_3 checked
List = [role_2, role_3];
I get the User like that:
#PutMapping("{id}")
public String update(#Valid User user, #PathVariable Long id){
user.setId(id);
userRepository.save(user);
return "redirect:/users";
}
And also in future I want to somehow check the current active Roles when passing them to the form.
Like that:
Current active roles are: role_2, role_3.
When I make a Get-request to show the form for editing, i have role_1 unchecked, and role_2 and role_3 checked.
My classes are as follows:
public class Role{
private String name;
//getters, setters, etc.
}
public class User{
#NotBlank(message="Please enter username")
private String username;
//getters, setters, etc.
}
Thanks for any help!! :)
I've found a way to get necessary information from the checkbox inputs:
use the #RequestParam annotation the following way:
Controller.java
public String update(#Valid User user, #RequestParam("roleIDs") List<Long> roles, #PathVariable Long id){
System.out.println(roles); //-the checked roles
return "redirect: /users";
}
Groovy .tpl file:
roles.each {role ->
label(for:role.name, role.name)
input(type:'checkbox', id:role.name, name:'roleIDs', value:role.id)
br()
}
Seems like I haven't googled the right question :)

grails 3 rest-api profile GET

I am using grails 3 rest-api profile. I created generated simple domain class
#Resource(readOnly = false, formats = ['json', 'xml'])
class User {
String name
String address
Date dateOfBirth
}
while http://localhost:8080/user/ will give me back json list of users, if I try http://localhost:8080/user/1 if gives back :
{"message":"Not Found","error":404}
what gives ?
actually I since found out that the url exposed is /user/show/1
since RestResource automatically exposes a controller based on grails.rest.RestfulController you can look in that class for the full list of methods
seems to be a bug. If you specify a uri it works:
#Resource(uri='/user/', readOnly = false, formats = ['json', 'xml'])
class User {
String name
String address
Date dateOfBirth
}

Play Framework 2.3 How to add unique constraint to sample application

Given the Play Framework 2.3 Computer Database sample application, I would like to practice adding a unique constraint on an attribute. Let's say I want the name attribute of the Computer class to be unique. I've tried to do this by adding a validate() function (and a getter) to Computer.java:
public List<ValidationError> validate() {
List<ValidationError> errors = new ArrayList<ValidationError>();
if(Computer.find.where().eq("name", getName()).findRowCount() != 0){
errors.add(new ValidationError("name", "Name must be unique. That value is already taken."));
}
return errors;
}
public String getName() {
return name;
}
This check works when creating new records in the database, however, this now causes a validation error when you update a Computer object but don't change the name. Is there a way to add a uniqueness constraint, similar to Rails? How can I validate uniqueness in Play?
Thanks!
UPDATE: see the answer by davide.
I ended up using the #Column(unique = true) constraint from the javax.persistence API. This doesn't generate an error in Play forms; instead, it throws a PersistenceException. Therefore I had to add change my controller to achieve the behavior I wanted. Both the create() and update() actions need a try/catch like this:
try {
computerForm.get().save();
} catch (PersistenceException pe) {
flash("error", "Please correct errors below.");
formData.reject("name", "Name conflict. Please choose a different name.");
return badRequest(createForm.render(computerForm));
}
UPDATE 2: each of the answers below is a possible solution
You need to exclude current entity from unique checking, i.e. like that:
if(Computer.find.where().eq("name", getName()).ne("id", getId()).findRowCount() != 0){
errors.add(new ValidationError("name", "Name must be unique."));
}
It will give you SQL query during update:
select count(*) from computer t0 where t0.name = 'Foo' and t0.id <> 123
And this during create:
select count(*) from computer t0 where t0.name = 'Foo' and t0.id is not null
P.S. ne() expression stands for Not Equal To and of course this approach assumes that your name field is Required
Edit: I sent you pull request with working solution, all you need is to add hidden field in your editForm like:
<input name="id" type="hidden" value='#computerForm("id").value'/>
Other thing is that you can simplify your model, i.e. don't need for getters for public fields.
I not sure if this answer your question, because I'm not familiar with Ruby syntax.
To "create a uniqueness constraint in the database" you can use the javax persistence API. Ebean will also recognize this.
To have a plain uniqueness constraint which involves a single field, you can use the #Column annotation:
#Entity
public class Computer extends Model {
...
#Column(unique = true)
public String name;
...
}
If you need some combination of fields to be unique, instead use the
#Table annotation
#Table(
uniqueConstraints=
#UniqueConstraint(columnNames={"name", "brand"})
)
#Entity
public class Computer extends Model {
...
public String name;
public String brand;
...
}
I hope it helps!

Unique Constraint over values of two domain classes in Grails

I have two domain classes. One is :
class User {
String login
String password
String firstName
String lastName
String address
String email
static constraints = {
login blank:false, size:5..15,matches:/[\S]+/, unique:true
password blank:false, size:5..15,matches:/[\S]+/
firstName blank:false
lastName blank:false
email email: true
}
}
And other is
class AddWebsite {
String website
User user
static constraints = {
website blank:false
website(unique: ['user'])
}
}
I am working with MongoDB at the backend. I need that for a particular login value, all siteURL values should be unique. Ex: login = abc#gmail.com. Then this user can have all unique url only in the database. But same urls can exist for different users. How do I do that using the unique constraint or any other approach?
Use embedded sub-documents to store SiteURL instances right inside the User. Then you define the collection to be a Set, which makes sure, all it's entries are unique. If you want to use the default mongo collection types or want to persist the order, define an interceptor like:
def beforeSave = {
urls = urls.unique()
}
UPDATE:
If your urls are plain strings, use the default primitive collection (no hasMany):
class User {
String login
//...
Set urls = new HashSet()
}
In this case you should be able to place unique constraint on the AddWebsite domain class such as this:
class AddWebsite {
String website
User user
static constraints = {
website(blank:false, unique: ['user'])
}
}
This will ensure that each website is unique in the database per user. Notice that multiple constraints are applied to the property website.
edited to match updated question.
It finally worked. I was getting the user cannot be null error while entering the website though it was not being validated in the AddWebsite domain class. I made the following changes and got it to work:
class AddWebsite{
String website
User user
static belongsTo = [user: User]
static constraints = {
website( url:true, unique: ['user'])
}
}
And in my controller also, I set the value of the user object to the session variable:
def addWebsites() {
if(request.method == 'POST') {
def w = new AddWebsite()
w.properties[
'website'
] = params
w.user = session["user"] //modified to make it work
if(w.save()) {
render view:'addWebsites', model:[message: "Successfully saved"]
}
else {
return [addWebsite:w]
}
}
Hope it helps someone :)

values cannot be entered for List<String> in create page of grails

This is the domain class:
package com.sample
class Person {
String id
String name
Integer age
Address address
List children
static hasMany = [pets:Pet, children: String, aliases : Alias]
static mapWith = "mongo"
static constraints = {
address nullable:true
}
}
This is the the create page of the app:
Can someone please tell me how I can get a list to write in the create Person page and a list editable in the edit Person page. (I'm using generated views by the command grails generate-view com.sample.Person)
First, you don't need the List children in domain class. But I'm not sure if grails supports scaffolding for relations with basic non-domain types (String in your case). If removing the list wouldn't help you will need to handle this situation manually.