let me describe a simple scenario:
There is a rest resource represented by this URI: /api/messages/{userid}. After the user is logged in, a request is dispatched passing to this URI "userid" (logged user). This way, as soon as the user logs in, he gets his own messages based on his ID.
If user is not logged yet, the URI is not visible (there is a authentication filter).
The problem is: if a already logged user discover this uri, he can submit a request passing another ID what will lead to a security problem because he will be able to get messages from another user (simply passing any random ID).
Can you propose any security model to prevent this security flaw ? (what I believe its more likely a cross-cutting concern).
Thanks, in advance!
Just off the top of my head, have the endpoint either (a) only return those messages visible by the logged-in user, or (b) only return messages if the logged-in user is the same as the userId in the URI. Which one depends on your business rules.
In the endpoint you can check for the user and then see if that user id matches the pathparam
finding the userid from the user name is as easy as selecting the id using the user name from a database
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
import ... blablabla;
#GET
#Path("messages/{userid}")
public Response getMessages( #PathParam("userid") int userId, #Context SecurityContext context ) {
String username = context.getUserPrincipal().getName();
int id = getIdFromName(username); // define this yourself
if(userId==id) {
// output the message list
List<Message> msgs = getDemMessagesGURL(); // define this yourself
return Response.ok(new GenericEntity<List<T>>(msgs) {}).build();
} else {
// output Forbidden(403)
return Response.status(403).build();
}
}
and with oauth you can use a servlet filter to set the user principal based on the oauth signiture
Related
I am using Facebook as external login of ASP.Net Core identity.
I would like, even if the user logged in with Facebook, the user to fill his profile on the website.
For that I use the ExternalLoginCallback method, from which I would like to get data from Facebook such as date of birth, location (country), ...
One issue is if the user unchecked some of the permissions, the default call to Facebook fails:
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
I would also need to do some additional checks on user data, which would require calling directly the Graph API.
My blocking points:
- In the ExternalLoginCallback method, I would need to separate the 'country' and 'birthday' to avoid the Facebook API to return an error in case of the user didn't grant the permission
- For that I would need the the user access_token (and for additional calls in the method), I don't see how to get it even if it is used by the Facebook Identity framework
- Once the profile created, I would like to get access to this access_token, which should be stored in the UserTokens table (I guess?), but I can't find it there, the table is empty. (my DbContext is a class extending IdentityDbContext<AppUser, AppRole, long>, don't know if it has an impact)
I have found this answer https://stackoverflow.com/a/42670559/4881677 which may help, but not sufficient.
Any help? :)
In order to store the user Facebook token, it requires to specify it in the options (not stored by default).
var fo = new FacebookOptions();
fo.SaveTokens = true;
From there we can call the graph method permissions to get the available permissions: https://graph.facebook.com/me/permissions?access_token={token}
Then it can be read with something like this:
foreach (var perm in data)
{
perms.Add((string)perm["permission"], (string)perm["status"]);
}
We use sitecore to manage our registered users (extranet domain) and when creating new virtual users we give it an email using the Profile.Email property and then call the Profile.Save() method.
Another property somewhere else reads the userProfile.Email, everything is fine at the beginning.
Plus we use Forms authentication with the remember me feature.
The problem is when we close the browser and reopen it Sitecore.Context.User contains info about the actual user who clicked remember me but the User.Profile always has the Email null.
I tried Reload() and initialize() they don't work. I also tried getting the user again via the username (User.FromName()) but the returned user object also doesn't have the Profile Email.
What is being done wrong?
There is one very important remark in Security API Cookbook. It is related to Sitecore 6 but as far as I know it should work with Sitecore 8 as there was no important changes in Security model. It worked for Sitecore 7.
Important You must log in a virtual user only after you assign Roles and Profile properties to them. The Roles and Profile properties that are assigned after logging in are lost upon subsequent request.
Sitecore.Security.Accounts.User user =
Sitecore.Security.Authentication.AuthenticationManager.BuildVirtualUser(#"domain\user"
, true);
if (user != null)
{
string domainRole = #"domain\role";
if (Sitecore.Security.Accounts.Role.Exists(domainRole))
{
user.Roles.Add(Role.FromName(domainRole));
}
user.Profile.Email = "user#domain.com";
user.Profile[“Custom Property”] = “Custom Value”;
user.Profile.Save();
Sitecore.Security.Authentication.AuthenticationManager.LoginVirtualUser(user);
}
I'm building a restricted signup. I want user with a specific code passed in a url to be able to signup and not others. I'm using the accounts package.
I can prevent account creation in the Accounts.onCreateUser method. I'm looking for a way to tell the server if the client had an authorised signup code. With a classic form (email+password) I can just add an extra hidden field. How can I achieve the same result if the user signs up with let's say Facebook?
Since Meteor doesn't use cookies, I can't store this info in a cookie that the server would access. Session variable are not accessible server side. And since I'm not controlling what got send with the account-facebook creation, I can't use a Session variable on the client side that I'd pass along when the user presses sign up.
Any idea"?
Just add the special token to the user object being passed to Accounts.createUser():
var user = {
email: email,
password: password,
profile: {
token: token
}
};
Accounts.createUser(user, function (error, result) {
if (error) {
console.log(error)
}
});
On the server side you can access this in the Accounts.onCreateUser():
Accounts.onCreateUser(function(options, user) {
console.log(options);
console.log(user);
});
I think it's in the options variable that you will find your token, so it would be options.profile.token.
for me, the best option here was passing in custom parameters to loginbuttons.
see the package docs:
https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3
Where it outlines the below:
accountsUIBootstrap3.setCustomSignupOptions = function() {
return {
mxpDistinctId: Session.get('mxpdid'),
leadSource: Session.get('leadSource')
}
};
Through the Websphere Console I've setup a Policy Set and a Policy Set Binding in order to support UsernameToken authentication on a webservice. As expected, it is rejecting web service calls without correct usernames and passwords. However, it is now accepting every user in the connected LDAP.
I would like to be able to only allow access to users in a specific LDAP group. I have the feeling that I need to create a custom JAAS Login in the Caller settings, but I'm not completely sure.
Does anybody have a solution for this, or a direction where I should be looking?
Edit: I'm doing this to expose an IBM BPM web service.
Create your web service based on EJB not a POJO, and then use #RolesAllowed annotation to specify roles which are allowed to invoke particular method from your service. Use adminconsole, scirpt or binding file to map defined role to user or groups from the LDAP server.
This is probably much easier than fighting with Login module and more flexible.
You can create a custom JAAS login module to use when consuming the username token. You can use a JAAS config that first calls the built-in token consumer, then your custom consumer. Doing it this way means that you can use the built-in consumer to parse the token and do timestamp and nonce processing and you only have to do the username/password validation in your own login module.
The instructions can be found here: http://www14.software.ibm.com/webapp/wsbroker/redirect?version=phil&product=was-nd-dist&topic=twbs_replace_authmethod_usernametoken
(Please forgive the formatting. I'm doing the best I can with what I have available here.)
Replacing the authentication method of the UsernameToken consumer using a stacked JAAS login module
By default, the Web services security UsernameToken consumer, UNTConsumeLoginModule, always validates the username and password that are contained within the token against the WebSphere registry. You can use the SPIs that GenericSecurityTokenFactory provides to bypass this authentication method.
About this task
If you want to replace the authentication method that UNTConsumeLoginModule uses, you must provide your own custom JAAS login module to do the authentication. The custom login module is stacked under UNTConsumeLoginModule in a custom JAAS configuration. The UNTConsumeLoginModule consumes and validates the token XML. The validation of the values provided for username and password is deferred to the custom stacked login module.
Because the use of UNTConsumeLoginModule carries with it the assumption that the username and password will be authenticated, more requirements are put on a stacked login module that intends to perform this function than are put on login modules that are only intended to provide dynamic token functionality.
To indicate to UNTConsumeLoginModule that it should not authenticate the username and password, you must set the following property on the configured callback handler:
com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true
Like most WS-Security login modles, UNTConsumeLoginModule always puts the consumed token in the shared state map to which all login modules in the stack have access. When authDeferred=true is specified, in the commit phase, UNTConsumeLoginModule ensures that the same UsernameToken object that had originally been put on the shared state has been put in another location in the shared state. If this UsernameToken object cannot be found, a LoginException occurs. Therefore, you cannot just set authDeferred=true on the callback handler without having an accompanying login module return the token to the shared state.
Procedure
Develop a JAAS login module to do the authentication and make it available to your application code. This new login module stacks under the com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule.
This login module must:
Use the following method to get the UsernameToken that UNTConsumeLoginModule consumes.
UsernameToken unt = UsernameToken)factory.getConsumerTokenFromSharedState(sharedState,UsernameToken.ValueType);
In this code example, factory is an instance of com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory.
Check the username and password in the manner that you choose.
You can call unt.getUsername() and unt.getPassword() to get the username and password.
Your login module should throw a LoginException if there is an authentication error.
Put the UsernameToken, that was obtained in the previous substep, back on the shared state.
Use the following method to put the UsernameToken back on the shared state.
factory.putAuthenticatedTokenToSharedState(sharedState, unt);
Following is an example login module:
package test.tokens;
import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import com.ibm.websphere.wssecurity.wssapi.token.UsernameToken;
import java.util.ArrayList;
import com.ibm.wsspi.security.registry.RegistryHelper;
import com.ibm.websphere.security.UserRegistry;
public class MyUntAuthenticator implements LoginModule {
private Map _sharedState;
private Map _options;
private CallbackHandler _handler;
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this._handler = callbackHandler;
this._sharedState = sharedState;
this._options = options;
}
public boolean login() throws LoginException {
//For the sake of readability, this login module does not
//protect against all NPE's
GenericSecurityTokenFactory factory = null;
WSSUtilFactory utilFactory = null;
try {
factory = GenericSecurityTokenFactory.getInstance();
utilFactory = WSSUtilFactory.getInstance();
} catch (Exception e) {
throw new LoginException(e.toString());
}
if (factory == null) {
throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
}
UsernameToken unt = (UsernameToken)factory.getConsumerTokenFromSharedState(this._sharedState,UsernameToken.ValueType);
String username = unt.getUsername();
char [] password = unt.getPassword();
//authenticate the username and password
//to validate a PasswordDigest password (fixpack 8.5.5.8 and later)
//String pw = yourCodeToLookUpPasswordForUsername(username);
//boolean match = utilFactory.verifyDigestedPassword(unt, pw.toCharArray());
//if (!match) throw new LoginException("Digested passwords do not match");
//Example:
try {
simpleUserGroupCheck(username, password, "cn=group1,o=ibm,c=us");
} catch (Exception e) {
LoginException le = new LoginException(e.getMessage());
le.initCause(e);
throw le;
}
//Put the authenticated token to the shared state
factory.putAuthenticatedTokenToSharedState(this._sharedState, unt);
return true;
}
private boolean simpleUserGroupCheck(String username, char [] password, String group) throws Exception {
String allowedGroup = null;
//get the default user registry
UserRegistry user_reg = RegistryHelper.getUserRegistry(null);
//authenticate the user against the user registry
user_reg.checkPassword(username, new String(password));
//get the list of groups that the user belongs to
java.util.List<String> groupList = user_reg.getGroupsForUser(username);
//you can either use a hard-coded group
allowedGroup = group;
//or get the value from your own custom property on the callback handler
//WSSUtilFactory util = WSSUtilFactory.getInstance();
//Map map = util.getCallbackHandlerProperties(this._handler);
//allowedGroup = (String) map.get("MY_ALLOWED_GROUP_1");
//check if the user belongs to an allowed group
if (!groupList.contains(allowedGroup)) {
throw new LoginException("user ["+username+"] is not in allowed group ["+allowedGroup+"]");
}
return true;
}
//implement the rest of the methods required by the
//LoginModule interface
}
Create a new JAAS login configuration.
In the administrative console, select Security > Global security.
Under Authentication, select Java Authentication and Authorization Service.
Select System logins.
Click New, and then specify Alias = test.consume.unt.
Click New, and then specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule
Click OK.
Click New, and then specify Module class name = test.tokens.MyUntAuthenticator
Select Use login module proxy.
Click OK, and then click SAVE.
Configure your UsernameToken token consumer to use the new JAAS configuration.
Open your bindings configuration that you want to change.
In the administrative console, select WS-Security > Authentication and protection.
Under Authentication tokens, select the UsernameToken inbound token that you want to change.
Select JAAS login = test.consume.unt.
Set the required property on the callback handler that is configured for the UsernameToken consumer.
Click Callback handler.
Add the com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true custom property.
Click OK.
Click SAVE.
Restart the application server to apply the JAAS configuration changes.
Test your service.
I'm using Parse.com and trying to set up user sign up with Facebook.
Upon authentication with Facebook for the first time a beforeSave is called on _User to fetch additional user details:
function UserBeforeSave(request, response){
var user = request.object,
auth = user.get('authData');
// check if user is newly registered
if (!user.existed()) {
// Check if a user signs up with facebook
if (Parse.FacebookUtils.isLinked(request.object)) {
// Query Graph API for user details
Parse.Cloud.httpRequest({
url:'https://graph.facebook.com/v2.2/me?access_token=' + auth.facebook.access_token,
success:function(httpResponse){
// Map facebook data to user object
if (httpResponse.data.first_name) request.object.set('first_name', httpResponse.data.first_name);
if (httpResponse.data.last_name) request.object.set('last_name', httpResponse.data.last_name);
if (httpResponse.data.email) request.object.set('email', httpResponse.data.email);
response.success();
},
error:function(httpResponse){
console.error(httpResponse);
response.error();
}
});
} else {
response.success();
}
} else {
response.success();
}
}
Problem is that that email line is actually breaking the operation with error:
Can't modify email in the before save trigger
I tried moving this code to the afterSave but the authdata is not available there making it difficult to call the FB API. The email is therefore left blank at the moment.
I'm assuming this is a very common use case of integrating Parse with the Facebook API, am I missing something in the integration process that automatically fetches the email?
I just do the graph query client-side and set email there. This works fine.
Is there a reason you want to do it in the before/afterSave on the User?