I want to use a custom authentication module conforming to JSR 196 in GlassFish 3. The interface javax.security.auth.message.ServerAuth has the method:
AuthStatus validateRequest(
MessageInfo messageInfo,
javax.security.auth.Subject clientSubject,
javax.security.auth.Subject serviceSubject
)
AuthStatus can be one of several constants like FAILURE or SUCCESS.
The question is: How can I get the roles from a "role datebase" with JSR 196?
Example: The server receives a request with a SSO token (CAS token for example), checks whether the token is valid, populates the remote user object with roles fetches from a database via JDBC or from REST service via http.
Is the role fetching in the scope of JSR 196? How could that be implemented?
Do I have to use JSR 196 together with JSR 115 to use custom authentication and a custom role source?
This is a code example from my JSR-196OpenID Implementation.
The method set the roles stored in a String Array for the current CallerPrincipal:
private boolean setCallerPrincipal(String caller, Subject clientSubject) {
boolean rvalue = true;
boolean assignGroups = true;
// create CallerPrincipalCallback
CallerPrincipalCallback cPCB = new CallerPrincipalCallback(
clientSubject, caller);
if (cPCB.getName() == null && cPCB.getPrincipal() == null) {
assignGroups = false;
}
try {
handler.handle((assignGroups ? new Callback[] {
cPCB,
new GroupPrincipalCallback(cPCB.getSubject(),
assignedGroups) } : new Callback[] { cPCB }));
logInfo(DEBUG_JMAC, "jmac.caller_principal:" + cPCB.getName() + " "
+ cPCB.getPrincipal());
} catch (Exception e) {
// should not happen
logger.log(Level.WARNING, "jmac.failed_to_set_caller", e);
rvalue = false;
}
return rvalue;
}
I call this method during the validateRequest() method.
You can see the complete code here:
http://code.google.com/p/openid4java-jsr196/source/browse/trunk/src/main/java/org/imixs/openid/openid4java/OpenID4JavaAuthModule.java
Also this page will be helpfull :
http://code.google.com/p/openid4java-jsr196/
Here's how I map users to roles:
I have 3 roles in my web.xml and also I have 3 role-to-group mappings in my sun-web.xml which map those roles several groups. Then I have a database with table Users that has a column called "group". That group corresponds to the group that is mapped to a role. I also use JSR 196-based custom auth module with OpenID. So basically whenever a user is logged in their group is read from the db and then my app assigns them the corresponding role. This is all done using the standard declarative security model of J2EE.
For my custom auth module I use a library called AuthenticRoast which makes things quite a bit simpler.
Here's also a related post...
Hope this helps.
Related
In Keycloak 8.0.1 we have a Realm with a Group and Subgroups like this:
group -
subgroup1
subgroup2
...
We need to insert a batch of subgroups and users into group. The subgroup should have some attributes.
How can I do this?
I tried:
Using an exported realm-export.json file with newly added subgroups and "Overwrite" on the import. Now I don't see how to connect the new user with the subgroup. And I am also not sure if old users will not be removed this way.
Calling the Keycloak REST API. It doesn't seem possible to UPDATE a group and add subgroups. Documentation says:
PUT /{realm}/groups/{id}Update group, ignores subgroups.
Now I am looking at using a UI testing tool to add the user programmatically, but this seems needlessly complex.
Is it possible to programmatically add new subgroups with users associated to that subgroup? Am I missing something with the REST API call or the import functionality? Is there maybe another way via for example the Java Admin Client?
You can create groups and subgroups under it , Here is the sample code to create subgroups using Admin Client. You can also associate users to those groups
public void addSubgroups() {
RealmResource realm =keycloak.realm("myrealm");
GroupRepresentation topGroup = new GroupRepresentation();
topGroup.setName("group");
topGroup = createGroup(realm, topGroup);
createSubGroup(realm,topGroup.getId(),"subgroup1");
createSubGroup(realm,topGroup.getId(),"subgroup2");
}
private void createSubGroup(RealmResource realm, String parentGroupId, String subGroupName) {
GroupRepresentation subgroup = new GroupRepresentation();
subgroup.setName(subGroupName);
try (Response response = realm.groups().group(parentGroupId).subGroup(subgroup)){
if (response.getStatusInfo().getFamily() == Family.SUCCESSFUL) {
System.out.println("Created Subgroup : " + subGroupName );
} else {
logger.severe("Error Creating Subgroup : " + subGroupName + ", Error Message : " + getErrorMessage(response));
}
}
}
private GroupRepresentation createGroup(RealmResource realm, GroupRepresentation group) {
try (Response response = realm.groups().add(group)) {
String groupId = getCreatedId(response);
group.setId(groupId);
return group;
}
}
getCreatedId(response);
This method in above answer (by ravthiru) is belongs to CreatedResponseUtil from package (org.keycloak.admin.client)
CreatedResponseUtil.getCreatedId(response);
The docs for cognito user pools can be found here:
http://docs.aws.amazon.com/cognito/latest/developerguide/how-to-manage-user-accounts.html
In this they do not say whether you can query users by the automatically generated sub attribute, which is a uuid. It explicitly says you can't search for users by custom attributes, but sub/uuid is not a custom attribute. Weirdly though, in the list of searchable attributes sub/uuid is not one of them. Surely though you can look up users by their UUID, how would this be done though??
You know, I have used COgnito but never needed to look up via sub (or other params other than the username). I looked into it because surely you can, but it is not very clear (like a lot of their documentation). Here is what I saw that you could try... hope it helps man.
// the imported ListUsersResult is...
import com.amazonaws.services.cognitoidp.model.ListUsersRequest;
import com.amazonaws.services.cognitoidp.model.ListUsersResult;
// class var
protected final AWSCognitoIdentityProviderClient identityUserPoolProviderClient;
// omitted stuff...
// initialize the Cognito Provider client. This is used to talk to the user pool
identityUserPoolProviderClient = new AWSCognitoIdentityProviderClient(new BasicAWSCredentials(AWS_ACCESS_KEY, AWS_SECRET_KEY)); // creds are loaded via variables that are supplied to my program dynamically
identityUserPoolProviderClient.setRegion(RegionUtils.getRegion(USER_POOL_REGION)); // var loaded
// ...some code omitted
ListUsersRequest listUsersRequest = new ListUsersRequest();
listUsersRequest.withUserPoolId(USER_POOL_ID); // id of the userpool, look this up in Cognito console
listUsersRequest.withFilter("sub=xyz"); // i THINK this is how the Filter works... the documentation is terribad
// get the results
ListUsersResult result = identityUserPoolProviderClient.listUsers(listUsersRequest);
List<UserType> userTypeList = result.getUsers();
// loop through them
for (UserType userType : userTypeList) {
List<AttributeType> attributeList = userType.getAttributes();
for (AttributeType attribute : attributeList) {
String attName = attribute.getName();
String attValue = attribute.getValue();
System.out.println(attName + ": " + attValue);
}
}
If you have the username you could get the user like this
// build the request
AdminGetUserRequest idRequest = new AdminGetUserRequest();
idRequest.withUserPoolId(USER_POOL_ID);
idRequest.withUsername(username);
// call cognito for the result
AdminGetUserResult result = identityUserPoolProviderClient.adminGetUser(idRequest);
// loop through results
I'm implementing a client to a web service (and the guys maintaining the web service have been a litte unresponsive..) I've used axis and WSDL2Java to generate java classes and I can call their login-method on their authentication-service ok, and get a sessionId back (eg z4zojhiqkw40lj55kgtn1oya). However, it seems that i cannot use this sessionId as a parameter anywhere. Even a call to their hasSession()-method directly after login returned false. I managed to solve this by setting setMaintainSession(true) on the Locator-object for this service. But the problem is, that this first service, the Authentication-service, is only used for authentification. If I then call setMaintainSession(true) on eg ProductServiceLocator, and call some method on it, I will get an error because of unauthenticated session. I have to find a way to share the session between the services on the client side.
Looking on their php code example-it seeems like they are storing the session in a cookie. How can I mimic this behaviour in my java client?
php-code:
$authentication = new SoapClient ( "https://webservices.24sevenoffice.com/authenticate/authenticate.asmx?wsdl", $options );
// log into 24SevenOffice if we don't have any active session. No point doing this more than once.
$login = true;
if (!empty($_SESSION['ASP.NET_SessionId'])){
$authentication->__setCookie("ASP.NET_SessionId", $_SESSION['ASP.NET_SessionId']);
try{
$login = !($authentication->HasSession()->HasSessionResult);
}
catch ( SoapFault $fault ) {
$login = true;
}
}
if( $login ){
$result = ($temp = $authentication->Login($params));
// set the session id for next time we call this page
$_SESSION['ASP.NET_SessionId'] = $result->LoginResult;
// each seperate webservice need the cookie set
$authentication->__setCookie("ASP.NET_SessionId", $_SESSION['ASP.NET_SessionId']);
// throw an error if the login is unsuccessful
if($authentication->HasSession()->HasSessionResult == false)
throw new SoapFault("0", "Invalid credential information.");
}
My code is the following:
AuthenticateLocator al = new AuthenticateLocator();
al.setMaintainSession(true);
Credential c = new Credential(CredentialType.Community,username,password,guid);
AuthenticateSoap s = al.getAuthenticateSoap();
String sessionId = s.login(c);
System.out.println("Session id was: "+sessionId);
System.out.println("Has Session: "+s.hasSession()); //Hooray, now works after setMaintainSession(true)
//And now trying to call another Service
CompanyServiceLocator cl = new CompanyServiceLocator();
cl.setMaintainSession(true);
CompanyServiceSoap css = cl.getCompanyServiceSoap();
css.getCountryList(); //FAILS!
So what can I do to make this work?
Hooray, I finally solved it myself :-D
Thanx a lot to the excellent article at http://www.nsftools.com/stubby/ApacheAxisClientTips.htm
I had to do the following with my code to make it work:
CompanyServiceLocator cl = new CompanyServiceLocator();
cl.setMaintainSession(true);
CompanyServiceSoap css = cl.getCompanyServiceSoap();
((Stub)css)._setProperty(HTTPConstants.HEADER_COOKIE, "ASP.NET_SessionId="+sessionId); //New line that does the magic
css.getCountryList(); //SUCCESS :-D
Operating in the high-level abstraction of the autogenerated classes, it was unknown to me that casting the service classes to Stub would expose more methods and properties that could be set. Good to know for later I guess :-)
I have a Portal that combines Spring controllers with a couple of regular servlets.
In the screen the user has a list from which he/she selects a credit card in order to see a report of the transactions of that card. As an extra security measure, I avoid sending the credit card in any request to the client so I have a list of credit cards numbers masked that I send to the user and in the request sent by the user I receive a record id which I use to determine which credit card is querying.
In a controller (ReportController), I have a method that process this input and call (locally) the servlet in charge of processing the report (ReportServlet). If there is any error in the processing it should return it to the screen using the model param "error". This last part isn't working.
If there is an error in the ReportServlet it is not returned to the screen. If I comment the forward line (and force an error) it works, but after doing the forwarding it doesn't. What I'm doing wrong?
Here's the code:
ReportController
try {
...
if (cardholders == null || cardholders.size() < 2 || id <= 0) {
model.put("error","there's an error");
return CARDTRANSACTIONS_PATH;
} else {
...
HttpUpdatetableRequestWrapper customRequest = new HttpUpdatetableRequestWrapper(request);
customRequest.setParameter("cardnumber", cardholder.getCardnumber());
request.getRequestDispatcher(config.getProperty("reportservlet")).forward((HttpServletRequest) customRequest, response);
String error = (String) session.getAttribute("error");
if(!(error == null || "".equals(error))){
throw new RuntimeException(error);
}
}
} catch (Exception ex) {
model.put("error", "there's an error");
} finally{ return CARDTRANSACTIONS_PATH;}
When your controller returns, Spring will give the model to an AbstractView which will decompose it and transfer the attributes over to the request. So when you are adding attributes to the model object in your controller, you're only adding them to a map with no relationship to the request. If you want those attributes to be available to the resource you forward to, you should add them directly to the request.
When you forward, you're expecting the resource you're forwarding to to handle the response, so you should make your controller return null in that situation, so that it doesn't do any further processing.
I'm doing a Dynamics CRM integration from a Java application and I've followed the example from the CRM training kit and managed successfully to connect and create accounts and contacts.
Now I'm having some problems with adding some more fields in the account creation and when connecting a contact with an account.
For instance I cannot create accounts with "address1_freighttermscode" that is a picklist.
My code is the following:
private static OrganizationServiceStub.Guid createAccount(OrganizationServiceStub serviceStub, String[] args) {
try {
OrganizationServiceStub.Create entry = new OrganizationServiceStub.Create();
OrganizationServiceStub.Entity newEntryInfo = new OrganizationServiceStub.Entity();
OrganizationServiceStub.AttributeCollection collection = new OrganizationServiceStub.AttributeCollection();
if (! (args[0].equals("null") )) {
OrganizationServiceStub.KeyValuePairOfstringanyType values = new OrganizationServiceStub.KeyValuePairOfstringanyType();
values.setKey("name");
values.setValue(args[0]);
collection.addKeyValuePairOfstringanyType(values);
}
if (! (args[13].equals("null"))){
OrganizationServiceStub.KeyValuePairOfstringanyType incoterm = new OrganizationServiceStub.KeyValuePairOfstringanyType();
incoterm.setKey("address1_freighttermscode");
incoterm.setValue(args[13]);
collection.addKeyValuePairOfstringanyType(incoterm);
}
newEntryInfo.setAttributes(collection);
newEntryInfo.setLogicalName("account");
entry.setEntity(newEntryInfo);
OrganizationServiceStub.CreateResponse createResponse = serviceStub.create(entry);
OrganizationServiceStub.Guid createResultGuid = createResponse.getCreateResult();
System.out.println("New Account GUID: " + createResultGuid.getGuid());
return createResultGuid;
} catch (IOrganizationService_Create_OrganizationServiceFaultFault_FaultMessage e) {
logger.error(e.getMessage());
} catch (RemoteException e) {
logger.error(e.getMessage());
}
return null;
}
When it executes, I get this error
[ERROR] Incorrect attribute value type System.String
Does anyone have examples on how to handle picklists or lookups?
To connect the contact with the account I'm filling the fields parentcustomerid and parentcustomeridtype with the GUID from the account and with "account", but the contact does not get associated with the account.
To set a picklist value you must use an OptionSet and for a lookup you must use an EntityReference. See the SDK's C# documentation, should work the same way using the Axis generated Java code.
incoterm.setKey("address1_freighttermscode")
//assuming the arg is an integer value that matches a picklist value for the attribute
OptionSetValue freight = new OptionSetValue();
freight.Value = args[13];
incoterm.setValue(freight);
collection.addKeyValuePairOfstringanyType(incoterm);
I haven't worked with Java for over a decade (and never towards an MS creation like Dynamics) so it might be way off from what you like. :)
You could use the REST web service and call directly to CRM creating your instances. As far I know, that's platform independent and should work as long as you can connect to the exposed service OrganizationData.