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();
}
Related
Need help to implement login via Vkontakte OAuth.
According to spring-boot docs made this:
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(vkClientRegistration());
}
private ClientRegistration vkClientRegistration() {
return ClientRegistration.withRegistrationId("vk")
.authorizationUri("https://oauth.vk.com/authorize")
.clientId("######")
.clientSecret("######")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("friends", "groups")
.tokenUri("https://oauth.vk.com/access_token")
// .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
// .userNameAttributeName(IdTokenClaimNames.SUB)
// .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Vkontakte")
.build();
}
As a result: context starts, localhost:8080 redirects to authorization page.
But after clicking "Allow" button browser comes back to login page with error:
[invalid_token_response] An error occurred while attempting to
retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized: [no
body]
I used VK API documentation from this:
https://dev.vk.com/api/access-token/authcode-flow-user
What should I correct to complete authorization flow?
Update
Debugging I found the reason of such behavior:
VK service returns access token like this:
{
"access_token":"ed0f46118dec0a5e6935ca198......",
"expires_in":86400,
"user_id":1111111,
"email":"1111222#mail.ru"
}
And it does not have property "token_type". As a result OAuth2AccessToken constructor fails.
public OAuth2AccessToken(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
Set<String> scopes) {
super(tokenValue, issuedAt, expiresAt);
Assert.notNull(tokenType, "tokenType cannot be null");
this.tokenType = tokenType;
this.scopes = Collections.unmodifiableSet((scopes != null) ? scopes : Collections.emptySet());
}
There is a solution which detailed describes configuring flow at the link (in Russian)
I've got the following set up:
Central auth server written with spring boot that is currently working (I can curl and receive an access token, jdbc token store, etc)
Multiple applications owned by the same developer, sharing the same customer base on different domains. IE: John Doe for app1 is the same as John Doe for app2.
I have an existing application (app1 above) that is jsf 2.2 with spring security configured for login purposes. That application works stand alone right now, with it's own user base.
This is the flow I am trying to obtain:
Resource Owner Password Credential OAuth Flow
So we would want:
User goes to app1
User enters user and password into app1 login page
User hits "login"
Some sort of configuration in Spring would then take the loginByUsername request, get access token from the central oauth server
We now have app1 access - the user could have one of three roles (ADMIN, USER, SUPERUSER).
When they go to (say) app1/views/createEntry.xhtml, we would confirm the access token we currently have is still active on the auth server.
The resource server would technically be the resources on the app1 server (right?)
I'm new to this oauth2.0 process (and spring really), but I think this is the flow I want. How do I set this up with Spring Security? I've seen a security setting called oauth2login() that I think is what we COULD want, but I think that is more authorization code flow.
I haven't found a very good example of this using the password flow.
I do trust each of the applications in this process, hence the password flow. We control the network that maintains traffic between the auth server and the other applications.
Edit: SSO isn't an option because of requirements and our customer base. The applications are unique enough that it doesn't make sense, but the user should be able to log into any of our applications with those credentials.
Edit 2: Sorry for second edit. I would like to add that I've added a resource configuration on app1 and it actually seems like it works - I've secured anything /views/* and when I attempt to go their, I get the expected "Full Authentication required" message.
Edit 3: I think I am making some progress -
First, I created a spring component that implements AuthenticationProvider and then overwrote the authenticate method so that I created a ResourceOwnerPasswordResourceDetails object with all my properties (client id, client secret, grant type, scope, etc) and called the authorization server to get a token. My excitement to see my log refresh for the authorization server was high.
Next step I need to figure out is how to generate an extension of org.springframework.security.core.userdetails.User so that I can store the privileges for the user.
Also - I can't quite figure out yet how the token is stored. I know the auth server generates a token and stores in jdbc, but where/how does the token get stored on the client side?
For those that were curious, here is how I set up the authentication provider on my client (app1). I still have issues with the resource server (ill ask a separate question), but here is what I did:
Custom authenticator:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AppUserDAO appUserDAO;
private String accessTokenUri = "http://localhost:8080/oauth/token";
private String clientId = "clientid";
private String clientSecret = "clientsecret";
public AccessTokenProvider userAccessTokenProvider() {
ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider();
return accessTokenProvider;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
List<String> scopes = new ArrayList<String>();
scopes.add("read");
final ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
resource.setUsername(username);
resource.setPassword(password);
resource.setAccessTokenUri(accessTokenUri);
resource.setClientId(clientId);
resource.setClientSecret(clientSecret);
resource.setGrantType("password");
resource.setScope(scopes);
// Generate an access token
final OAuth2RestTemplate template = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest()));
template.setAccessTokenProvider(userAccessTokenProvider());
OAuth2AccessToken accessToken = null;
try {
accessToken = template.getAccessToken();
System.out.println("Grabbed access token from " + accessTokenUri);
}
catch (OAuth2AccessDeniedException e) {
if (e.getCause() instanceof ResourceAccessException) {
final String errorMessage = String.format(
"While authenticating user '%s': " + "Unable to access accessTokenUri '%s'.", username,
accessTokenUri);
throw new AuthenticationServiceException(errorMessage, e);
}
throw new BadCredentialsException(String.format("Access denied for user '%s'.", username), e);
}
catch (OAuth2Exception e) {
throw new AuthenticationServiceException(
String.format("Unable to perform OAuth authentication for user '%s'.", username), e);
}
// Determine roles for user
List<GrantedAuthority> grantList = ...
// Create custom user for the principal
User user = .....
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, null /*dont store password*/, grantList);
return token;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
....
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
}
I am new to the Authorized.net API and facing the below E00007, User authentication failed due to invalid authentication values. in authorized.net. I am using the authorize.net version to call the GetTransactionDetails API.
Any idea on what is the issue ? Any help ?
<dependency>
<groupId>net.authorize</groupId>
<artifactId>anet-java-sdk</artifactId>
<version>1.8.6</version>
</dependency>
and I am using the below code
public class GetTransactionDetails {
public static final String apiLoginId= "6LaBc8HJ6Q";
public static final String transactionKey= "XXXXXXXX";
public static void main(String[] args) {
ApiOperationBase.setEnvironment(Environment.SANDBOX);
MerchantAuthenticationType merchantAuthenticationType = new MerchantAuthenticationType() ;
merchantAuthenticationType.setName(apiLoginId);
merchantAuthenticationType.setTransactionKey("transactionKey");
ApiOperationBase.setMerchantAuthentication(merchantAuthenticationType);
//need valid transacaction Id to run
String transId = "60024624183";
GetTransactionDetailsRequest getRequest = new GetTransactionDetailsRequest();
getRequest.setMerchantAuthentication(merchantAuthenticationType);
getRequest.setTransId(transId);
GetTransactionDetailsController controller = new GetTransactionDetailsController(getRequest);
controller.execute();
GetTransactionDetailsResponse getResponse = controller.getApiResponse();
TransactionDetailsType transactionDetailsType = getResponse.getTransaction();
if (getResponse!=null) {
if (getResponse.getMessages().getResultCode() == MessageTypeEnum.OK) {
System.out.println(getResponse.getMessages().getMessage().get(0).getCode());
System.out.println(getResponse.getMessages().getMessage().get(0).getText());
System.out.println("---------------------------------------");
System.out.println("Auth Amount : "+transactionDetailsType.getAuthAmount());
System.out.println("Auth Code : "+transactionDetailsType.getAuthCode());
System.out.println("Response Reason Description : "+transactionDetailsType.getResponseReasonDescription());
System.out.println("Transaction Status : "+transactionDetailsType.getTransactionStatus());
System.out.println("Submit Date : "+transactionDetailsType.getSubmitTimeLocal());
}else{
System.out.println("Failed to get transaction details: " + getResponse.getMessages().getResultCode());
List<Message> messages = getResponse.getMessages().getMessage();
for (Message message : messages) {
System.out.println("Code : "+message.getCode());
System.out.println("Text : "+message.getText());
}
}
}
}
}
output:
06/02/17 00:35:48,733: INFO [pool-1-thread-1] (net.authorize.util.LogHelper:24) - Use Proxy: 'false'
Failed to get transaction details: ERROR
Code : E00007
Text : User authentication failed due to invalid authentication values.
Can you try removing the quotes from setTransactionKey
merchantAuthenticationType.setTransactionKey("transactionKey");
change to
merchantAuthenticationType.setTransactionKey(transactionKey);
There are multiple causes of this error, which may include:
Using the wrong API Login ID (the "name" field in some of the API calls)
Using the wrong Transaction Key
Using the wrong API endpoint. Authorize.net has two API endpoints, one for sandbox and one for production:
Sandbox API Endpoint: https://apitest.authorize.net/xml/v1/request.api
Production API Endpoint: https://api.authorize.net/xml/v1/request.api
To test your Authorize.net authentication credentials, go to [Authorize.net's API documentation site] and choose API Endpoints and Authentication, then the test your authentication credentials box.1
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;
}