I am using a (little modified) workaround from this course, to fetch the userId, which is null if the request was sent from an Android client.
/**
* This is an ugly workaround for null userId for Android clients.
*
* #param user A User object injected by the cloud endpoints.
* #return the App Engine userId for the user.
*/
private static String getUserId(User user) {
String userId = user.getUserId();
if (userId == null) {
LOG.info("userId is null, so trying to obtain it from the datastore.");
AppEngineUser appEngineUser = new AppEngineUser(user);
ofy().save().entity(appEngineUser).now();
AppEngineUser savedUser = ofy().load().key(appEngineUser.getKey()).now();
userId = savedUser.getUser().getUserId();
LOG.info("Obtained the userId: " + userId);
}
return userId;
}
Although I am not able to get the userId.
INFO: Obtained the userId: null
This workaround has already worked perfectly in other projects, so the problem must be elsewhere. My endpoints api is annotated with the following scopes, clientIds and audiences:
scopes = {
Constants.EMAIL_SCOPE
},
clientIds = {
Constants.API_EXPLORER_CLIENT_ID,
Constants.WEB_CLIENT_ID,
Constants.ANDROID_CLIENT_ID
},
audiences = {
Constants.ANDROID_AUDIENCE
}
Constants.ANDROID_AUDIENCE and Constants.WEB_CLIENT_ID are the same. I am not using a web client, but Google told me to add a web client id. Does this client id need to have redirect uris and javascript origins specified?
In my Android client I am using the following to specify the audience.
mCredential = GoogleAccountCredential.usingAudience(
EndpointService.this,
"server:client_id:IDIDIDID.apps.googleusercontent.com"
);
Please help me to figure this one out.
I just understood why this workaround works. I need to begin a new objectify session so the cache is not used and the userId can be populated.
Objectify objectify = ofy().factory().begin();
AppEngineUser savedUser = objectify.load();
Related
I am trying to upgrade an application (it should fetch emails from a mailbox every few minutes) from Microsoft EWS deprecated API to the new Graph API, but I am facing some issues.
This is my class for the connector :
public class O365graphApiConnector {
private final GraphServiceClient<Request> graphClient;
public O365graphApiConnector(String clientId, String username, String password) {
final UsernamePasswordCredential usernamePasswordCredential =
new UsernamePasswordCredentialBuilder()
.clientId(clientId)
.username(username)
.password(password)
.build();
final TokenCredentialAuthProvider tokenCredentialAuthProvider =
new TokenCredentialAuthProvider(usernamePasswordCredential);
graphClient=GraphServiceClient.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
}
public User getUserProfile() {
return graphClient.me().buildRequest().get();
}
public MessageCollectionPage getOutlookEmails() {
return graphClient.me().messages().buildRequest().get();
}
}
I am using com.azure:azure-identity:1.4.2 and com.microsoft.graph:microsoft-graph:5.8.0.
I build the connector, passing the clientId, username and password. I am able to call getUserProfile , and I am getting something, so the authentication "works".
However, I get a 404 when calling getOutlookEmails :
SEVERE: Throwable detail:
com.microsoft.graph.http.GraphServiceException: Error code:
ResourceNotFound Error message: Resource could not be discovered.
GET https://graph.microsoft.com/v1.0/me/messages SdkVersion :
graph-java/v5.8.0
404 : Not Found [...]
When I run this in debug mode and intercept the token, it seems to be OK though : I have a bunch of rights that my admin has given to the applicative account :
"scp": "EWS.AccessAsUser.All Mail.Read Mail.Read.Shared Mail.ReadBasic Mail.ReadWrite
Mail.ReadWrite.Shared Mail.Send Mail.Send.Shared MailboxSettings.ReadWrite User.Read User.Read.All User.ReadWrite profile openid email"
This is part of what we see on the admin side (more rights were added after the screenshot was taken) :
My understanding is that this should be enough to get access to the emails of the given mailbox programmatically, but apparently, it's not.
Any idea of what I am missing ?
actually, the technical "user" I am using didn't really have a mailbox (despite the user name being an email address.. that confused me).
It had been given the permissions on the given mailbox I am interested in though, so the fix is simply to select the mailbox/user before retrieving the messages :
public MessageCollectionPage getOutlookEmailsFor(String mailbox) {
return graphClient.users(mailbox).messages().buildRequest().get();
}
I have a Java web application which do SPNEGO authentication of clients in a Windows Active Directory environment.
To authenticate the user we use code from the good old SPNEGO SourceForge project.
String encodedAuthToken = (String) credentials;
LOG.debug("Encoded auth token: " + encodedAuthToken);
byte[] authToken = B64Code.decode(encodedAuthToken);
GSSManager manager = GSSManager.getInstance();
try {
Oid krb5Oid = new Oid("1.3.6.1.5.5.2");
GSSName gssName = manager.createName(_targetName, null);
GSSCredential serverCreds = manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, krb5Oid, GSSCredential.INITIATE_AND_ACCEPT);
GSSContext gContext = manager.createContext(serverCreds);
if (gContext != null) {
while (!gContext.isEstablished()) {
authToken = gContext.acceptSecContext(authToken, 0, authToken.length);
}
if (gContext.isEstablished()) {
// Login succeeded!
String clientName = gContext.getSrcName().toString();
}
}
}
The authentication works good but we also have a requirement to delegate the user credentials to a back-end service (Exchange EWS), using constrained delegation.
When configuring this in our AD it looks like a small difference, but it's not. See:
AD delegation settings
The difference is described here: msdn.microsoft.com/en-us/library/cc246080.aspx?f=255&MSPPError=-2147217396
With unconstrained delegation we could simply use the available delegated credentials when we call the back-end service and it would all be good:
GSSCredential delegatedCreds = gContext.getDelegCred()
SpnegoHttpURLConnection conn = new SpnegoHttpURLConnection(clientCreds);
With constrained delegation we have no access to the users TGT and it seems we need to use the MS-SFU (S4U2proxy) Kerberos extension which Java 8 is suppose to support.
The only example I could find is this one: https://github.com/ymartin59/java-kerberos-sfudemo (thanks Yves Martin for that!)
Now to my problem... After my authentication I basically end up with the username of the authenticated user (see "clientName" in code above).
Do we really need to use the S4U2self mechanism to impersonate the user here?
The client just sent us it's Kerberos Service Ticket (wrapped in the SPNEGO token I can't decode).
Ideally we should be able to use that service ticket and my own service's TGT to authenticate the user (using the S4U2proxy mechanism)?
But I do not understand how.
So now I'm wondering if it's possible to tie together our SPNEGO authentication with S4U2proxy delegation?
Many thanks for any input on this.
I've actually been doing something like this recently but am using spring security kerberos. I put an example on github here. The key thing that I found that I needed set up to use constrained delegation like you want it and S4U2Proxy was to make sure (if you're using Oracle/OpenJDK) you set isInitiator=true in your JAAS Config so that when getDelegCred is called you get back a Krb5ProxyCredential. See comment here. With that credential, you can use it to create service ticket tokens on the Users behalf for the services you are constrained to use in the normal fashion, like this.
I've done a lot of investigation on Kerberos constrained delegation, and finally I've figured out the correct way of doing it using Java.
Settings on Domain Controller
1) No Delegation: Do not trust this account for delegation
You (service user) can not get delegated credentials of the user. It means you can not perform any task on end user's behalf.
At the most you can do is to accept the incoming ticket from the user(usually browser) and get it verified by passing it to KDC. In response, KDC will tell you for which user(or principal) this ticket is issued to, but no credentials will be passed.
2) Unconstrained Delegation: Trust this account for delegation to any service (Kerberos only)
With this option, you (service user) get the delegated credentials of the user. Moreover, what you get is a TGT of the user. Using this TGT, you can request TGS (service ticket) on user's behalf for any service.
3) Trust this account for delegation to specified services (Kerberos only)
Here, you specify the services to which you can use the delegated credentials. It means when this option is enabled, you get the delegated credentials, however, you are allowed to use them only to get end user's TGS for the specified services.
Another important point is, you must have end user's TGS (end user's TGS for your web app). Then using this TGS, you can request KDC the end user's TGS for another service.
4) Trust this account for delegation to specified services (Any Protocol)
This is also known as protocol transition. In this option also, you need to specify the services for which you can request the TGS to KDC on user's behalf.
You (service user) are allowed to 'impersonate' the end user, without having any kind of ticket from the end user. You can impersonate any user, and get TGS for the specified services.
This option is useful for backgroung processes or schedulars where end user interaction is not possible.
Java Code Samples
1) Getting Delegated Credentials (useful in option 2 and 3 stated above)
// ---------------------------------
// step 1: Login using service user credentials and get its TGT
// ---------------------------------
Subject subject = new Subject();
Krb5LoginModule krb5LoginModule = new Krb5LoginModule();
Map<String,String> optionMap = new HashMap<String,String>();
optionMap.put("keyTab", "c:\\ticket\\sapuser.keytab");
optionMap.put("principal", "HTTP/TEST"); // SPN you mapped to the service user while creating the keytab file
optionMap.put("doNotPrompt", "true");
optionMap.put("refreshKrb5Config", "true");
optionMap.put("useTicketCache", "true");
optionMap.put("renewTGT", "true");
optionMap.put("useKeyTab", "true");
optionMap.put("storeKey", "true");
optionMap.put("isInitiator", "true"); // needed for delegation
optionMap.put("debug", "true"); // trace will be printed on console
krb5LoginModule.initialize(subject, null, new HashMap<String,String>(), optionMap);
krb5LoginModule.login();
krb5LoginModule.commit();
// ---------------------------------
// Step 2: Use login context of this service user, accept the kerberos token (TGS) coming from end user
// ---------------------------------
public GSSCredential validateTicket(byte[] token) {
try {
return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token));
}
catch (PrivilegedActionException e) {
throw new BadCredentialsException("Kerberos validation not successful", e);
}
}
private class KerberosValidateAction implements PrivilegedExceptionAction<GSSCredential> {
byte[] kerberosTicket;
public KerberosValidateAction(byte[] kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}
#Override
public GSSCredential run() throws Exception {
byte[] responseToken = new byte[0];
GSSName gssName = null;
GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
while (!context.isEstablished()) {
responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
gssName = context.getSrcName();
if (gssName == null) {
throw new BadCredentialsException("GSSContext name of the context initiator is null");
}
}
//check if the credentials can be delegated
if (!context.getCredDelegState()) {
SecurityLogger.getLogger().error("Credentials can not be delegated. Please make sure that delegation is enabled for the service user. This may cause failures while creating Kerberized application.");
return null;
}
// only accepts the delegated credentials from the calling peer
GSSCredential clientCred = context.getDelegCred(); // in case of Unconstrained Delegation, you get the end user's TGT, otherwise TGS only
return clientCred;
}
}
// ---------------------------------
// Step 3: Initiate TGS request for another service using delegated credentials obtained in previous step
// ---------------------------------
private Object getServiceTicket(GSSCredential clientCred) throws PrivilegedActionException {
Object o = Subject.doAs(new Subject(), (PrivilegedExceptionAction<Object>) () -> {
GSSManager manager = GSSManager.getInstance();
Oid SPNEGO_OID = new Oid("1.3.6.1.5.5.2");
Oid KRB5_PRINCIPAL_OID = new Oid("1.2.840.113554.1.2.2.1");
GSSName servicePrincipal = manager.createName("HTTP/TEST", KRB5_PRINCIPAL_OID); // service to which the service user is allowed to delegate credentials
ExtendedGSSContext extendedContext = (ExtendedGSSContext) manager.createContext(servicePrincipal, SPNEGO_OID, clientCred, GSSContext.DEFAULT_LIFETIME);
extendedContext.requestCredDeleg(true);
byte[] token = new byte[0];
token = extendedContext.initSecContext(token, 0, token.length); // this token is the end user's TGS for "HTTP/TEST" service, you can pass this to the actual HTTP/TEST service endpoint in "Authorization" header.
return token;
});
return o;
}
2) Getting Impersonated Credentials (useful in option 4 stated above)
Initial steps are similar as mentioned inside step 1 above. You need to login using service user credentials. There is small change in 'run' method, which is given below:
#Override
public GSSCredential run() throws Exception {
GSSName gssName = null;
GSSManager manager = GSSManager.getInstance();
GSSCredential serviceCredentials = manager.createCredential(GSSCredential.INITIATE_ONLY);
GSSName other = manager.createName("bhushan", GSSName.NT_USER_NAME, kerberosOid); // any existing user
GSSCredential impersonatedCredentials = ((ExtendedGSSCredential) serviceCredentials).impersonate(other);
return impersonatedCredentials;
}
}
You can see that we don't need user's TGS in this case.Getting TGS on user's behalf for other service, is same as mentioned in step 3 given in above code. Just pass these impersonatedCredentials instead of delegatedCredentials.
I hope this will be helpful.
Thanks,
Bhushan
I'm having trouble continuing an OAuth session using a token obtained on an iOS client from a back-service. Specifically it looks to be a permission problem:
iOS Client Obtains Access Token (ObjC / FB iOS SDK v3.24)
Session established with the following permissions:
[FBSession openActiveSessionWithReadPermissions:#[
#"email",
#"user_about_me",
#"user_friends",
#"user_birthday",
#"public_profile" . . .
On completion . . .
FBSession *session = [FBSession activeSession];
NSString *accessToken = [session.accessTokenData accessToken];
Access Token Sent to Backend (which is Spring Boot + Kotlin)
A Spring FacebookTemplate is instantiated using the token obtained above, as follows:
#Test fun testFacebookTemplate()
{
val facebook = FacebookTemplate("$$TOKEN_FROM_FACEBOOK_IOS_SDK$$")
//Raises exception . .
val profile = facebook.userOperations().userProfile
println("Profile: " + profile)
}
The OAuth session established on the iOS client is continued from the backend successfully, and eg, a Facebook friend list can be returned. However, attempting to retrieve the profile, as shown above raises an error:
Error from Facebook: {"error":{"message":"(#3) Application does not have the capability to make this API call." , "type":"OAuthException","code":3,"fbtrace_id":"B4C+eS3n2PW"}}
DEBUG o.s.s.f.a.impl.FacebookErrorHandler - Facebook error:
DEBUG o.s.s.f.a.impl.FacebookErrorHandler - CODE : 3
DEBUG o.s.s.f.a.impl.FacebookErrorHandler - TYPE : OAuthException
Question:
Which permission is missing to return the User object. This does not appear to be documented in Spring's FacebookTemplate
Is this requested during OAuth authentication/authorization ( in my case with the FB iOS SDK) or via the developer console? This is unclear to me because both the openActiveSessionWithPermissions and the definition of the application in Facebook's web console contain references to these permissions.
It appears as though Spring's FacebookTemplate v2.0.2.RELEASE has some permission related error when invoking the request for user profile against the Facebook Graph API v2.3
As a work-around use:
val profile = facebook.fetchObject("me", User::class.java,
"id", "first_name", "last_name", "email");
After facebook API change, field "first_name" was replaced by field : "name"
public FacebookUser getFacebookUserData() {
Facebook facebook = new FacebookTemplate(accessToken);
String[] fields = {"id", "name", "email"};
FacebookUser user = facebook.fetchObject("me", FacebookUser.class, fields);
return user;
}
where FacebookUser is :
public class FacebookUser {
String id;
String name;
String email;
public FacebookUser(){ }
public FacebookUser(String id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
I am using custom authentication using credentials and ipaddress, however, I need to display user firstname and lastname on UI. Whereas I am not using UserDetails and I am using UsernamePasswordAuthenticationToken, how can I get access to firstname and lastname, where UserDetails bean saved in Details.
Account account = (Account)accountDao.loadUserByUsername(username);
if(!account.getPassword().equals(password)) {
logger.debug("[USER AUTHENTICATION]Invalid Password:"+password);
return null;
}
logger.warn(String.format("[USER AUTHENTICATION]%s %s",new Object[]{account.getFirstName(),account.getLastName()}));
isAuthenticatedByIP = false;
for(AllowedIPAddress allowedIpAddress:account.getAllowedIPs()){
if(allowedIpAddress.getIpAddress().equals("0.0.0.0")||allowedIpAddress.getIpAddress().equals(userIPAddress)) {
isAuthenticatedByIP = true;
break;
}
}
// Authenticated, the user's IP address matches one in the database
if (isAuthenticatedByIP)
{
logger.debug("[USER AUTHENTICATION]isAuthenticatedByIP is true, IP Addresses match");
UsernamePasswordAuthenticationToken result = null;
result = new UsernamePasswordAuthenticationToken(account.getUsername(), account.getPassword(), account.getAuthorities()) ;
result.setDetails(account);
return result
} else {
logger.warn("[USER AUTHENTICATION]User IP not allowed "+userIPAddress);
}
how to get fields of account in jsp for displaying welcome message for user.
In spring mvc you can make a custom userDetail bean as session scoped bean and set your require values in it after successful login like firstname,lastname etc.Use that value wherever you want.
The question is very simple. I'd like to restrict user access with same login from different machines/browsers: only one live user session is possible.
Apache shiro library is used for user authentification and managment.
Of course this could be done using simple synchornized maps and etc. But the question is: Has Apache Shiro special mechanisms for that or not?
Another variant of this question: how to reveice the list of all subjects who are logged in the system using apache shiro?
UPD:
To clarify my question. My desire is to have some code like this (I known, that there isn't such class exception, but the idea must be more clean):
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(login, password);
try {
currentUser.login(token);
} catch (AlreadyAuthenticatedException aae) {
errorMsg = "You should logoff on another machine!";
}
The Shiro sessions are stored in SessionDAO with sessionId as keys. Without extra effort you cannot access a session by a principal (user name). However, you could extend DefaultSecurityManager and check all active sessions by SessionDAO.getActiveSessions.
The following codes could be a simple example (suppose you are not using WebSubject):
public class UniquePrincipalSecurityManager extends org.apache.shiro.mgt.DefaultSecurityManager {
#Override
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
String loginPrincipal = (String) token.getPrincipal();
DefaultSessionManager sm = (DefaultSessionManager) getSessionManager();
for (Session session : sm.getSessionDAO().getActiveSessions()) {
SimplePrincipalCollection p = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (p != null && loginPrincipal.equals(p.getPrimaryPrincipal())) {
throw new AlreadyAuthenticatedException();
}
}
return super.login(subject, token);
}
}