I've done considerable research it doesn't work for me for some reason I don't even get into the situation where I get "zombie" instances(like existing in session but not in db anymore - I wish I had this situation) since remove() is not propagated into database. The child record continues to exist inside my db.
Consider two tables: User and Token(FK_User_Id) , relationship one-to-many respectively
Inside DAO final code which doesn't work:
public void deleteToken(Token t) {
Token b = em.merge(t);
em.remove(b); // does nothing
em.flush();
}
Inside controller:
Object obj;
Token token;
obj = tokenService.getByString(urlToken); // query returning detached object
User user;
if (obj != null) {
token = (Token) obj; // Detached, query is "fetch" overriding lazy init, so token contains its user parent
user = token.getUser(); // simply get the user to which this token belongs
if ((token.getExpiryDate().compareTo(new GregorianCalendar()
.getTime())) == 1) {
userService.activateUser(user); // user gets merged inside and its relevant property is changed and propagated to db successfully
tokenService.deleteToken(token); // this line calls DAO method described in snippet above - it fails to delete the token, but no error or exception - record simply stays in database
model.addAttribute("activationSuccess", "true");
}...
User entity:
public class User {
public static final String FIND_USER_BY_TOKEN = "findUserByToken";
public static final String FIND_USER_BY_USERNAME = "findUserByUsername";
public static final String FIND_USER_BY_EMAIL = "findUserByEmail";
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval=true)
private List<Token> tokens = new ArrayList(); ...
Token entity:
#Entity
#Table(name = "token")
#NamedQueries({
#NamedQuery(name=Token.FIND_TOKEN_BY_STRING, query="Select t From Token T where t.tokenString=:string")
})
public class Token {
public static final String FIND_TOKEN_BY_STRING = "findTokenById";
#Id
#GeneratedValue
Long id;
#ManyToOne(optional=true)
private User user; ...
Now if I call something like:
User c = b.getUser();
em.remove(c);
Inside DAO snippet, it deletes both token and user which is not what I want. Only token must be deleted.
Basically what I am trying to do is to retrieve Token by string property and along with it the user parent which owns this token. Then I retrieve this user from token and change some property for the user(changes propagated into db). All successful so far. Then I want to delete the token as a final operation but in a way that user will not be deleted.
I am on my fifth hour on this please help... I managed to setId to null for the token(propagated into db), but it only gets me the point where token no longer has owner but still persists in database. To delete it I tried to merge the Token inside DAO with null which threw me exception. Then I tried to set Tokens list value to null inside User(which was retrieved from this token) and it also through me exception.
How I am supposed to delete child token entity which I retrieved with its parent but keep parent present in db?
SQL Hibernate log shows no delete query...after passing remove method.
Thanks,
If you do not dereference the token from User's list of tokens, cascade persist (you have cascade all set which includes persist) will cause the token to be resurrected at some point. You must clear all references to the child, especially ones marked cascade persist when you want to remove entities. Something like:
if ((token.getExpiryDate().compareTo(new GregorianCalendar()
.getTime())) == 1) {
user.getTokens().remove(token);
userService.activateUser(user);
//tokenService.deleteToken(token); //no longer needed due to orphanremoval being true
model.addAttribute("activationSuccess", "true");
...
Related
I'm currently trying to implement an simple CRUD integrated with Active Directory via LDAP, using Spring Data, for managing my internal users.
The problem is, while the reading works as expected, any writing on AD (creating or editing a user, for example) results in a generic error message, shown below:
[LDAP: error code 53 - 0000209A: SvcErr: DSID-031A107A, problem 5003 (WILL_NOT_PERFORM), data 0\n\u0000]; remaining name 'DC=company, DC=com'
The ldap connection is being made using LDAPS with an admin user. I can even work with the same credentials without any issues in a simple nodejs test application. So I'm probably making some mistake with Spring Data.
The relevant source code is attached below.
Entity class:
// Person.java (Entity model)
#Data
#Entry(
base = "ou=Employees,dc=company,dc=com",
objectClasses = {"person", "top"}
)
public class Person {
#Id
private Name dn;
#Attribute(name = "cn")
private String commonName;
#Attribute(name = "sAMAccountName")
private String accountName;
#Attribute(name = "userPrincipalName")
private String username;
#Attribute(name = "mail")
private String mail;
#Attribute(name = "userPassword")
private String password;
#Attribute(name = "description")
private String desc;
#Attribute(name = "memberOf")
private List<String> groups;
#Attribute(name = "company")
private String company;
#Attribute(name = "objectClass")
private List<String> objectClasses;
#Attribute(name = "objectCategory")
private String objectCategory;
}
Repository class:
// PersonRepository.java
#Repository
public interface PersonRepository extends LdapRepository<Person> {
Person findByMailIgnoreCase(String mail);
}
Service class:
#Service
public class UserService {
#Autowired
private PersonRepository personRepository;
/**
* Save the user at AD.
*
* #param username the user login name
* #param name the user name and surename
* #param companyExtName the company external name
* #param email the user email
* #param description the user description
* #return the newly created user
*/
public Person createPerson(String username, String name, String companyExtName,
String email, String description) {
final Person user = new Person();
user.setAccountName(username);
user.setCommonName(name);
user.setCompany(companyExtName);
user.setMail(email);
user.setUsername(email);
String tempPass = RandomStringUtils.randomAscii(10);
user.setPassword(digestSHA(tempPass));
user.setDn(LdapNameBuilder.newInstance("DC=company, DC=com")
.build());
List<String> objClasses = new ArrayList<>();
objClasses.add("person");
objClasses.add("top");
user.setObjectClasses(objClasses);
user.setObjectCategory("CN=Person,CN=Schema,CN=Configuration,DC=company,DC=com");
List<String> groups = new ArrayList<>();
groups.add("CN=Administrators,CN=Builtin,DC=company,DC=com");
user.setGroups(groups);
if (description != null && !description.isEmpty()) {
user.setDesc(description);
}
return personRepository.save(user);
}
/**
* Encodes the user password as it is used at Active Directory
*
* #param plain the plain text password
* #return the password hash
*/
private static String digestSHA(String plain) {
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
digester.update(plain.getBytes());
return String.format("{SHA}%s", Base64.getEncoder().encodeToString(digester.digest()));
} catch (NoSuchAlgorithmException ex) {
return null;
}
}
The exception is thrown when I call personRepository.save(user);
As a addtional information, I've already tried a few variations of the code attached -- tried to remove almost all user data beforing saving it, different password encodings and hashing -- but the result is always the same.
Any help on this will be greatly appreciated.
Thanks!
EDIT:
Investigation indicates that the cause is probably something related with the way I'm sending my user DN.
Anyway, I'm still wrestling with this issue.
I was able to create/edit my Active Directory users with a workaround.
In my UserService, instead of using the Spring Data Ldap repository, I've used the LdapTemplate methods, like shown below.
// UserService.java
public void createPerson() {
Name userDn = LdapNameBuilder
.newInstance()
.add("ou", ou)
.add("cn", accountName)
.build();
DirContextAdapter context = new DirContextAdapter(userDn);
context.setAttributeValue("cn", accountName);
context.setAttributeValue("sn", accountName);
context.setAttributeValue("userPassword", digestSHA(password));
context.setAttributeValue("company", company);
context.setAttributeValue("description", desc);
context.setAttributeValue("mail", mail);
context.setAttributeValue("sAMAccountName", accountName);
context.setAttributeValue("userPrincipalName", username);
context.setAttributeValue("objectCategory", objectCategory);
context.setAttributeValues("objectClass", objectClasses.toArray());
DirContextAdapter context = user.getLdapContext("Users");
ldapTemplate.bind(context);
}
Since I used the same values for user creation with both Spring Data and LdapTemplate, my original issue is probably related to some treatment Spring does before sending the data to my Active Directory server.
Since the method above is currently working for me, I'll follow with it. When I have some spare time I'll go back to this to find out what I was doing wrong with Spring.
For future use, I believe it is related to memberOf attribute. This attribute must be set after the user is created, but it seems that Spring Data is filling this property with an empty string even if I set the attribute to null when creating the user.
Lots of articles says that it is because of a lack of SSL connection to a LDAP server.
Here are some links:
stackoverflow.com/questions/17290539
stackoverflow.com/questions/6797955
forum.spring.io/forum/spring-projects/data/ldap
community.oracle.com/thread/2177638
I am trying to make a call out from salesforce most of the code is copied from another working package.
Can anyone tell me why the call out method below is never run?
I am saving into my custom table before and after the call out method is called but the saving into my custom table does not get called with in the call out method.
public class AutoSyncConnector {
public AutoSyncConnector()
{
}
public void Fire(string jsonToPost)
{
// 1. Authentication send the current session id so that request can be validated
String sessionId = UserInfo.getSessionId();
// 2. warp the request and post it to the configured end point
// This is how to get settings out for custom settings list
String connectorUrl = ASEndPoints__c.getValues('MainUrlEndPoint').autosync__MainSiteUrl__c;
CastlesMessageLog__c cd = new CastlesMessageLog__c();
cd.SentJson__c = 'before call out this is called';
insert cd;
AutoSyncConnector.CallOut(jsonToPost, connectorUrl);
CastlesMessageLog__c cd2 = new CastlesMessageLog__c();
cd2.SentJson__c = 'after call out this is called';
insert cd2;
}
public static void CallOut(String jsonToPost, String connectorUrl)
{
MakeCallout(jsonToPost,connectorUrl);
}
#future(callout=true)
public static void MakeCallout(String jsonToPost, String connectorUrl){
CastlesMessageLog__c cd = new CastlesMessageLog__c();
cd.SentJson__c = 'start inside before call out this is never called';
insert cd;
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setTimeout(120000);
// string authorizationHeader = 'Check I can add stuff to the header';
String sfdcConnectorUrl = connectorUrl + '/api/autosyncwebhook';
req.setEndpoint(sfdcConnectorUrl);
//req.setHeader('Authorization', authorizationHeader);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(jsonToPost);
h.send(req);
CastlesMessageLog__c cd2 = new CastlesMessageLog__c();
cd2.SentJson__c = 'end inside before call out this is never called';
insert cd2;
}
}
Go to Setup -> Monitoring -> Apex jobs. My gut feel is that you'll see lots of "uncommitted work pending" errors in there.
When you make any DML (insert/update/delete) you open a transaction with the database. If the next thing you'll do is a callout (which can have max timeout time 120 seconds) it'll mean you hold a lock on this record (or even whole table) for very long time. SF has no way of knowing whether the call will succeed or will have to be rolled back. So they protect the situation by banning such code right away ;)
Make callout first, then your DML.
Or make DML, call #future (that's the purpose, to switch to another thread, separate the contexts) and if the callout comes back with error - do whatever cleanup you'd consider a rollback (delete the record? update it to status = Sync failed? Send email to user / insert for him a Task to retry later?)
I am using Angular in an ASP.NET Core with ASP.NET Identity application.
I have the following controller action
[HttpGet("users/{userId:int:min(1)}/notes"), Authorize]
public async Task<IActionResult> GetNotesBy(userId) {
var data = _service.getNotesBy(userId);
return Ok(data);
} // GetNotesBy
I would like to restrict the access to the API so:
If a user is authenticated than it can only access its notes.
I want to prevent an authenticate user with ID=X to access the notes of a user with ID=Y. How can I block an user in this situation?
This is what resource based authorization is aimed at.
As resource based authorization requires the actual resource it needs to happen imperatively, inside your controller.
The following is for ASP.NET Core RC1.
So, let's assume your getNotesBy returns a Notes class, and you have a few operations, read, write, update, delete.
First we need to define the operations. There's a suitable base class in Microsoft.AspNet.Authorization.Infrastructure, OperationAuthorizationRequirement. So we'd do something like this.
public static class Operations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement { Name = "Create" };
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = "Read" };
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement { Name = "Update" };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = "Delete" };
}
So now we have our operations, we think about how we handle authorization. You have two ways operations can succeed, if the current user owns the notes, or the current user is an admin. This equates to two handlers for a single requirement/operation.
The admin one is easy, it would look something like this;
public class AdminAuthorizationHander :
AuthorizationHandler<OperationAuthorizationRequirement, Notes>
{
protected override void Handle(AuthorizationContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
var isSuperUser = context.User.FindFirst(c => c.Type == "Superuser" &&
c.Value == "True");
if (isSuperUser != null)
{
context.Succeed(requirement);
return;
}
}
}
Here we're looking for a Superuser claim with a value of True. If that's present we succeed the requirement. You can see from the method signature we're taking the OperationAuthorizationRequirement and a resource, the Notes class. This handler doesn't limit itself to a single operation, admins have rights to every operation.
Now we can write the handler which looks for the actual user.
public class NotesAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Notes>
{
protected override void Handle(AuthorizationContext context,
OperationAuthorizationRequirement requirement,
Notes resource)
{
if (context.User.Name == resource.Owner)
{
context.Succeed(requirement);
}
}
}
Here we are writing something that will work for all resources, and checks an Owner property on the resource against the name of the current user.
So we have two handlers now for a single requirement, the OperationAuthorizationRequirement.
Now we need to register our handlers. In startup.cs you register handlers in DI in the ConfigureServices() method. After the call to services.AddAuthorization() you need to put your handlers into DI. You would do this like so;
services.AddSingleton<IAuthorizationHandler, AdminAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, NotesAuthorizationHandler>();
You can adjust the scope from Singleton to whatever you like if you are taking things like a DbContext.
Finally we're almost ready to call this, but first you need to change your controller constructor to take an instance of IAuthorizationService. Once you have that you can call AuthorizeAsync() and away you go.
[Authorize]
public class NotesController : Controller
{
IAuthorizationService _authorizationService;
public NotesController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
[HttpGet("users/{userId:int:min(1)}/notes"), Authorize]
public async Task<IActionResult> GetNotesBy(userId)
{
var resource = _service.getNotesBy(userId);
if (await authorizationService.AuthorizeAsync(User, resource, Operations.Read))
{
return Ok(data);
}
return new ChallengeResult();
}
}
So what you are doing is getting your resource, and then authorizing the current user against it and the operation. When this happens all handlers which can handle that resource and operation will get called. As there are multiple handlers any one can succeed and allow access.
Hi I´m new using GAE and JPA, and I´m having some problems trying to update an entity. I copy next a code example:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
private String userName;
#ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
private Address address;
}
When I save on datastore a User instance it's store without problems.
After this, I retrive this instance from the datastore, then I set a new userName and try to update it.
public User updateUser(User user) {
EntityManager mgr = getEntityManager();
if (!containsUser(user)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(user);
}
The update is performed, the new userName is stored in the dataStore, but as the Address field has FetchType.LAZY I'm losing the persisted value on the update.
How can I make an update of some fields without losing other values?
Try getting and saving the entity in the same method, otherwise you will lost the session and the manager will recognize the lazy attributes as null ones.
Hope it helps!
I'm execute method Datastore.delete(key) form my GWT web application, AsyncCallback had call onSuccess() method .Them i refresh http://localhost:8888/_ah/admin immediately , the Entity i intent to delete still exist. Smilar to, I refresh my GWT web application immediately the item i intent to delete still show on web page.Note the the onSuccess() had been call.
So, how can i know when the Entity already deleted ?
public void deleteALocation(int removedIndex,String symbol ){
if(Window.confirm("Sure ?")){
System.out.println("XXXXXX " +symbol);
loCalservice.deletoALocation(symbol, callback_delete_location);
}
}
public AsyncCallback<String> callback_delete_location = new AsyncCallback<String>() {
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
public void onSuccess(String result) {
// TODO Auto-generated method stub
int removedIndex = ArryList_Location.indexOf(result);
ArryList_Location.remove(removedIndex);
LocationTable.removeRow(removedIndex + 1);
//Window.alert(result+"!!!");
}
};
SERver :
public String deletoALocation(String name) {
// TODO Auto-generated method stub
Transaction tx = Datastore.beginTransaction();
Key key = Datastore.createKey(Location.class,name);
Datastore.delete(tx,key);
tx.commit();
return name;
}
Sorry i'm not good at english :-)
According to the docs
Returns the Key object (if one model instance is given) or a list of Key objects (if a list of instances is given) that correspond with the stored model instances.
If you need an example of a working delete function, this might help. Line 108
class DeletePost(BaseHandler):
def get(self, post_id):
iden = int(post_id)
post = db.get(db.Key.from_path('Posts', iden))
db.delete(post)
return webapp2.redirect('/')
How do you check the existence of the entity? Via a query?
Queries on HRD are eventually consistent, meaning that if you add/delete/change an entity then immediately query for it you might not see the changes. The reason for this is that when you write (or delete) an entity, GAE asynchronously updates the index and entity in several phases. Since this takes some time it might happen that you don't see the changes immediately.
Linked article discusses ways to mitigate this limitation.