I am currently trying to implement Spring's ACLs into my existing application.
Sadly i am stuck at a specific point which seems to be caused by my UserDetailsService.
The problem/error is the following when i call the createAcl() function of the
MutableAcl class like this:
public void addPermission(long objectId, Sid recipient, Permission permission, Class clazz) {
MutableAcl acl;
ObjectIdentity oid = new ObjectIdentityImpl(clazz.getCanonicalName(), objectId);
try {
acl = (MutableAcl) mutableAclService.readAclById(oid);
} catch (NotFoundException nfe) {
acl = mutableAclService.createAcl(oid);
}
acl.insertAce(acl.getEntries().size(), permission, recipient, true);
mutableAclService.updateAcl(acl);
}
Inside of this function a new ObjectIdentity is created, if this class instance does not yet have one. A new PrincipalSid is created from the current Authentication object for this purpose (saved inside the ACL_SID table). Like this:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
PrincipalSid sid = new PrincipalSid(auth);
this.createObjectIdentity(objectIdentity, sid);
The problem occurs when it tries to save the entry into the ACL_SID table which is defined as this:
CREATE TABLE IF NOT EXISTS ACL_SID (
id BIGSERIAL NOT NULL PRIMARY KEY,
principal BOOLEAN NOT NULL,
sid VARCHAR(100) NOT NULL,
CONSTRAINT UNIQUE_UK_1 UNIQUE(sid,principal)
);
As you can see the sid is a VARCHAR with 100 characters. My custom User class contains a few properties which are mostly converted to a String representation, which causes the PrincipalSid to be longer than 100 characters. Now the first obvious solution would be to just change the toString method to only return the most essential values.
Still this seems kinda "hacky" to me. Is there a better solution?
The sid column in ACL_SID table should only contain the username, not the entire user object with its properties. To create a correct instance of PrincipalSid, make sure at least one of following is true:
auth.getPrincipal() returns an instance of UserDetails interface.
auth.getPrincipal().toString() returns the username.
Related
1. For example UserProfile which has 3 properties name,dob, age
2. And 2nd class let's say UserProfileResponse which has only "id"
public ResponseEntity<UserProfileResponse> createUserProfile(#RequestBody UserProfile userProfile)
{
UserProfileResponse userProfileResponse = new UserProfileResponse();
userProfileResponse.setId(??) // How do I set ID?
**createUserProfileData(userProfile) /// This is used to create DB record**
return new ResponseEntity<UserProfileResponse>(userProfileResponse,HTTPStatus.OK);
}
So for this userProfileResponse.setId(??) how can I set the ID value?
can I directly do like this userProfileResponse.setId(userProfileResponse.getId());
Or I can Pass one more request body like this
ResponseEntity<UserProfileResponse> createUserProfile(#RequestBody UserProfile userProfile, #RequestBody ID)
Thanks in advance.
You can call createUserProfileData method and return the id of the newly inserted object from it.
In createUserProfileData method, you can call saveAndFlush method of the repository which will save the userProfile Object.
This will return the id of the newly inserted object.
Finally your code will look like below:
public ResponseEntity<UserProfileResponse> createUserProfile(#RequestBody UserProfile userProfile)
{
UserProfileResponse userProfileResponse = new UserProfileResponse();
int id = createUserProfileData(userProfile)
userProfileResponse.setId(id)
return new ResponseEntity<UserProfileResponse>(userProfileResponse,HTTPStatus.OK);
}
If you want to get a value from the RequestBody UserProfile which it didn't contain it actually.Sorry it's impossible.
And we only could receive one requestBody at one time,so we need to use some other ways to collect the info.There is some other solutions:
Use #PathVariable to get ID from url
Use #RequestParam to get Id from requestParam
Add a new field named Id into your UserProfile
Use other way that that could get your Id,this depend how you persistent or generate the Id.
In your case,I'm not sure what you are going to do with the id.
If the "createUserProfileData" method means you need to offer an id first for persistence.
Well,I dont know which database and what kind of framework you are using.As I know,most of framework and database has the ability to generate the id automatically.But if you insist to generate id by your self,I recommend you UUID.
If the "createUserProfileData" method is saving the UserProfile to the database literally and the id is generated by the database itself,then you just do it and put the Id represent the record you just saved to UserProfileResponse.
As to how to get the id represent the record you just saved?It's up to the framework you're using and precisely how the code is written.
In my application I can identify user by providerId and providerUserId. But initially, I have only following information:
providerId,
accessToken,
secret.
Thus, I need to acquire providerUserId by this information.
I'm trying to use following code:
ConnectionData connectionData = newConnectionData(providerId, accessToken, secret);
ConnectionFactory<?> connectionFactory = connectionFactoryLocator.getConnectionFactory(providerId);
Connection<?> connection = connectionFactory.createConnection(connectionData);
if(connection.test()) {
connection.sync();
} else {
throw new AuthException();
}
return userEntityService.findOneByConnectionKey(connection.getKey());
But problem is that connection key is not initialized: providerUserId is null.
How can I acquire it in this case?
Generally, this code is intended to be used internally by Spring Social's connection framework (e.g., ConnectController, ConnectionRepository, ConnectionFactory, etc). Normally, you wouldn't use it directly unless you were looking to extend the framework or achieve something that the framework doesn't do for you.
Provider ID is determined by the connection factory being used. For example, the FacebookConnectionFactory defines it as "facebook". For Twitter it's "twitter". The value isn't terribly important, except that it be (1) consistently used for all connections against the same provider and (2) it be unique among all providers. Generally, it's good to just use the provider's name in all lowercase.
The access token is obtained by going through the OAuth "dance" (e.g., a series of redirects and prompts to obtain user authorization). ConnectController handles this for you...so does ProviderSignInController. If the token is an OAuth2 token, there will be no secret. If it's an OAuth 1.0(a) token, then you'll be given the secret along with the token at the end of the dance.
A bit late, however, if you are following the spring-social "philosophy", there is a UserConnection table. You can query it for providerUserId.
The schema is in JdbcUsersConnectionRepository.sql:
-- This SQL contains a "create table" that can be used to create a table that JdbcUsersConnectionRepository can persist
-- connection in. It is, however, not to be assumed to be production-ready, all-purpose SQL. It is merely representative
-- of the kind of table that JdbcUsersConnectionRepository works with. The table and column names, as well as the general
-- column types, are what is important. Specific column types and sizes that work may vary across database vendors and
-- the required sizes may vary across API providers.
create table UserConnection (userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
rank int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(512) not null,
secret varchar(512),
refreshToken varchar(512),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);
I've managed to create number of readonly Web Api OData services following the tutorials here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api. I'm therefore employing the ODataConventionModel builder to create the model from a set of entities (incidentally coming from a Telerik ORM). This all seems to work fine and I can happily issue queries, view the metadata and so forth on the service.
I've now tried to turn my attention to the other CRUD operations - firstly Create and have stumbled into a problem! Namely, the Post method fires correctly (CreateEntity) but the entity parameter is null - by doing a check against the ModelState.IsValid, it shows that the problem is a null ID (key) value. This is unsurprising because the database uses a Database Generated Identity for the ID column and therefore the ID would be created when the entity is saved into the database context.
I've therefore tried all sorts of ways of marking the ID column as database generated, but haven't managed to find anything. Strangely, I can't seem to find even one post of someone asking for this - surely I can't be the only one?!
I noted that when looking at the EF modelbuilder (for example here: http://forums.asp.net/t/1848984.aspx/1) there appears to be a means of affecting the model builder with a .HasDatabaseGeneratedOption property, but no similar option exists in the System.Web.Http.OData equivalent.
So the questions therefore are:
Is there a means of altering the model builder (or something else) so that the controller will accept the object and deserialize the entity even with a null key value?
If so, how can I do this?
If not, any suggestions as to other options?
I realise that I could potentially just populate the object with an (in this case) integer value from the client request, but this seems a) semantically wrong and b) won't necessarilly always be possible as a result of the client toolkit that might be used.
All help gratefully received!
Many thanks,
J.
You need to create a viewmodel for insert which does not contain the ID parameter. Use Automapper to map the properties of the incoming insert-model to your data entities.
The problem that you're having is that ID is a required attribute in your data model because it is your PK, except during insert, where it shouldn't be specified.
In my case, my database-generated key is a Guid.
As a work-around, in my TypeScript client code, I submit (via http POST) the object with an empty Guid like this: Note: ErrorId is the key column.
let elmahEntry: ELMAH_Error = {
Application: 'PTUnconvCost',
Host: this.serviceConfig.url,
Message: message,
User: that.userService.currentUserEmail,
AllXml: `<info><![CDATA[\r\n\r\n${JSON.stringify(info || {})}\r\n\r\n]]></info>`,
Sequence: 1,
Source: source,
StatusCode: 0,
TimeUtc: new Date(Date.now()),
Type: '',
ErrorId: '00000000-0000-0000-0000-000000000000'
};
Then, in my WebApi OData controller, I check to see if the key is the empty guid, and if so, I replace it with a new Guid, like this:
// POST: odata/ELMAH_Error
public IHttpActionResult Post(ELMAH_Error eLMAH_Error)
{
if (eLMAH_Error.ErrorId == Guid.Empty)
{
eLMAH_Error.ErrorId = Guid.NewGuid();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.ELMAH_Error.Add(eLMAH_Error);
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
if (ELMAH_ErrorExists(eLMAH_Error.ErrorId))
{
return Conflict();
}
else
{
throw;
}
}
return Created(eLMAH_Error);
}
I am working on creating a custom membership class for my asp.net project. In the MembershipProvider class, function for user validation is defined as follows.
public abstract bool ValidateUser(string username, string password);
The problem is validation can be failed for several reason, so I want to provide more detailed description as to why login failed (e.g., user is lockedout etc) but that cannnot be done via a bool return type. I certainly can create one of my method for validation as follows
public RichStatusDetailsEnum ValidateUser(string username, string password);
Problem with above is that I will not be able to call this method in a generic way (just by Membership.ValidateUser). Rather I will have to instantiate object of my custom membership provider etc etc. Does anyone else has encountered same issue and if there are any better ways of handling this type of situation?
As per this MSDN resource, the return will tell you if it is valid. There are only two parameters (username and password). If it is a false return, then you know one of them is off. You can always test if the UserName is correct by calling Membership.GetUser(). If the MembershipUser object returned is not null, then you know the UserName is correct. In which case, that would lead you to believe that if ValidateUser() fails, it is a bad password.
To see if the user is locked out: for the MembershipUser object that gets returned from GetUser(), you can test if the user is locked out by the property Membershipuser.IsLockedOut.
Description
One way to get this done is
Store the reason, why the validation failed, in the HttpContext.Current.Items Collection
If ValidateUser failed, get the data and do something (like add an error to ModelState.AddModelError("Property","Message");
Sample
public override bool ValidateUser(string username, string password)
{
// your custom logic goes here
HttpContext.Current.Items["ValidateUserResult"] = ValidateUserFailed.UserNameWasWrong;
return false;
}
public enum ValidateUserFailed
{
UserNameWasWrong, PasswordWasWrong, BecauseIDontLikeYou
}
your other class
More Information
MSDN - HttpContext.Items Property
MSDN - ViewDataDictionary.ModelState Property
I have a controller that calls a JpaRepository derived query method, I thought it was supposed to be case sensitive by default and from google I've only found options to add case insensitivity to the query, but not the case sensitivity. In my code it finds and returns rows from database regardless of case. Is it a bug or i don't know something ?
Controller code:
System.out.println(nickname);
User invitedUser = userService.findByNickname(nickname);
System.out.println(invitedUser);
JpaRepository code:
Optional<User> findByNicknameEquals(String nickname);
I have tried just findByNickname but result was the same
UserService code:
public User findByNickname(String nickname){
Optional<User> result = userRepository.findByNicknameEquals(nickname);
User user;
if(result.isPresent()){
user = result.get();
}else{
user = null;
}
return user;
}
what gets printed by the sout() from controller:
Fixed by changing default charset and collation on database tables:
ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE utf8_bin;
before this I had this charset, this is what made it ignore case.
ENGINE=InnoDB DEFAULT CHARSET=latin1;