I am writing application that need to read mailbox using IMAP, but as daemon, without user interaction. I need to use OAuth2 to get access.
Because I need it without user interaction, I need to use client credentials flow. This was added this June.
I have done everything from official documentation. Registered application, added permissions, added mailbox permission using PowerShell.
When I get request access token with scope https://outlook.office365.com/.default, the one that I receive has role IMAP.AccessAsApp, so I believe that is correct. I used https://jwt.ms/ to parse JWT.
The problem is when I try to authenticate using this access token in Java, for example
Properties props = new Properties();
props.put("mail.imap.ssl.enable", "true");
props.put("mail.imap.auth.mechanisms", "XOAUTH2");
props.put("mail.debug", "true");
Session session = Session.getInstance(props);
Store store = session.getStore("imap");
store.connect("outlook.office365.com", 993, "testing#mydomain.com", "accessToken");
I receive AUTHENTICATE failed. I tried same code with access token received using authorization code flow, which requires user interaction. Using that access code I was able to connect to mailbox. So the code is correct.
I even tried using client id and service id instead of email address as username, but without success.
I am not sure where I made the mistake and if I am using correct username. Any help is appreciated.
I wrote same answer here, so I am coping it here.
I think I made some progress.
I read documentation few times, tried few times from the start with same error. I even have tried using client and object ids instead of email as username, in lack of better ideas.
So this is where I think I have made mistake previous times.
On the part where it is needed to register service principal, I needed to execute
New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
Here I have put enterprise application object id as ServiceId argument. And that is ok.
But on
Add-MailboxPermission -Identity "email address removed for privacy reasons" -User
<SERVICE_PRINCIPAL_ID> -AccessRights FullAccess
I have put my registered application object id as User argument. I also tried setting object id of enterprise application, but it did not have success.
When I executed
Get-ServicePrincipal -Organization <ORGANIZATION_ID> | fl
I did not pay attention to ServiceId property, even with documentation specifying it and saying it will be different.
Now I cleared everything and started fresh.
I have executed all the steps again, but on the step for creating new service principal I used data from enterprise application view. When I need to add mail permission, I list service principals, and then use ServiceId value from the output, as argument for user.
With that, I was able to authorise.
Thanks everyone for sharing your experience. This has proved to be a little confusing. :)
To sum everything up, to access a mailbox with IMAPS and OAuth2 (as opposed to using Graph API which is another method Microsoft recommends):
Create an Azure App Registration
Add API permission Office 365 Exchange Online - IMAP.AccessAsApp and grant admin consent
Create a service principal, which will be used to grant mailbox permissions to in Exchange Online
Connect-AzureAD
Connect-ExchangeOnline
$azapp = Get-AzureADApplication -SearchString 'App Registration Name'
$azsp = Get-AzureADServicePrincipal -SearchString $azapp.DisplayName
# GOTCHA: You need the ObjectId from 'Enterprise applications' (Get-AzureADServicePrincipal), not 'Application registrations' (Get-AzureADApplication) for ServiceId (thanks #[jamie][1])
$sp = New-ServicePrincipal -AppId $azapp.AppId -ServiceId $azsp.ObjectId -DisplayName "EXO Service Principal for $($azapp.DisplayName)"
Grant access rights to mailboxes for the service principal
$mbxs = 'mymbx1#yourdomain.tld',`
'mymbx2#yourdomain.tld',`
'mymbx3#yourdomain.tld'
$mbxs | %{ Add-MailboxPermission -Identity $_ -User $sp.ServiceId -AccessRights FullAccess } | fl *
Get-MailboxPermission $mbxs[-1] | ft -a
You can use Get-IMAPAccessToken.ps1 to test your setup
.\Get-IMAPAccessToken.ps1 -TenantID $TenantId -ClientId $ClientId -ClientSecret $ClientSecret -TargetMailbox $TargetMailbox
Other parameters you may need:
Authority: https://login.microsoftonline.com/<YourTenantId>/
Scope: https://outlook.office365.com/.default
Related
I'm finding a way to programatically list Google Cloud projects inside an organization. I'm trying to use a service account exported json credential to achieve such purpose in this way:
// More info on the endpoint here:
// https://cloud.google.com/resource-manager/reference/rest/v1/projects/list
final CloudResourceManager cloudResourceManagerService = createCloudResourceManagerService();
final CloudResourceManager.Projects.List listRequest = cloudResourceManagerService
.projects()
.list()
.setFilter("labels.it-restoring:false name:IT-TEST-*");
final ListProjectsResponse listResponse = listRequest.execute();
if (listResponse.isEmpty()) {
throw new RuntimeException("The API did not get any response"); // I never get past here
}
log.info("Listing projects returned: {}", listResponse);
The problem I find is that I always get an empty response. Even though I assigned the service account the role of owner. According to docs, I could use roles/
resourcemanager.organizationAdmin which I also set but with no luck. I create the CloudResourceManagement api object using getApplicationDefault.
However if I do gcloud beta auth application-default login which triggers an auth flow in the browser and authenticate with the user which is the owner of the organization this works and lists all the projects that I have.
Can anybody explain to me what I should do to store a proper credential which would emulate he user owner? I already set the service account with the Owner role which in theory gives virtually access to all resources and still no luck.
In order to list the projects on your organization, you need the permission resourcemanager.projects.get. Please find more information in this link The service account might have the owner role of 1 project, and not enought to list them all.
An alternative solution is to grant the account the cloudasset.assets.searchAllResources permission at org level by using one of the following roles:
roles/cloudasset.viewer
roles/cloudasset.owner
roles/viewer
roles/editor
roles/owner
With this permission, you can list all the projects within an organization 456:
gcloud asset search-all-resources \
--asset-types="cloudresourcemanager.googleapis.com/Project"
--scope=organizations/456
Documentation: https://cloud.google.com/asset-inventory/docs/searching-resources
Related post: How to find, list, or search resources across services (APIs) and projects in Google Cloud Platform?
I have a java application running in ECS in which I want to read data from table in account 1 (source_table) and write it to a table in account 2 (destination_table). I created two dynamodb clients with different credential providers - for source_table client I'm using an STSAssumeRoleSessionCredentialsProvider with the arn of a role in account 1; for destination client I'm using DefaultAWSCredentialsProviderChain.
The assume role bit works and I'm able to read using the source client but using the destination client does not work - it still tries to use the assumed role credentials when trying to write to destination_table and fails with unauthorized error (assumed-role is not authorized to perform Put Item).
I tried using EC2ContainerCredentialsProviderWrapper on the destination client but same error.
Should this work? Or are the credentials shared under the hood which makes it impossible to have two different AWSCredentialProviders running simultaneously like this?
I noticed this answer which uses static credentials and apparently works, so I'm at a loss why this doesn't work.
I figured it out with some help from AWS support. It was a problem with my IAM configuration on the role in account 2. I was misled by the error message which said 'assumed-role is not authorized to perform Put Item' when in fact my original account 2 role itself was unable to do so.
I have a simple application written in C#.
That application works on windows machine and user account logged to the Active Directory.
In that application I retrive some information from LDAP Active Directory.
In C# I do connection with LDAP in that way:
DirectoryEntry de = new DirectoryEntry("LDAP://OU=places,DC=system,DC=org");
and next I retrieve needed data (for example):
DirectorySearcher searcher = new DirectorySearcher(de);
searcher.Filter = "(telephone=111222333)";
SearchResultCollection resultCollection = searcher.FindAll();
As you see in that application I do not need log to the LDAP with username and password but only I need to put the correct path:
The constructor of DirectoryEntry looks like that:
public DirectoryEntry(
string path
)
Of course there is a constructor with username and password, but as I said I do not need to use it:
public DirectoryEntry(
string path,
string username,
string password
)
I tried to retrieve the same information in Java and I used the similar code:
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://ad.host:389");
env.put(Context.SECURITY_PRINCIPAL, "username");
env.put(Context.SECURITY_CREDENTIALS, "password");
DirContext ldap = new InitialDirContext(env);
and next I retrieve needed data (for example):
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration tel = ldap.search("OU=places,DC=system,DC=org", "(telephone=111222333)", searchControls);
But there is a one difference. As you see I need to put username and password in Java if I want to connect to that LDAP Active Directory.
If I do not put the username and password I will see the following error:
Exception in thread "main" javax.naming.NamingException:[LDAP: error code 1 - 000004DC: LdapErr: DSID-0C0906E8, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v1db1;] remaining name 'OU=places,DC=system,DC=org'
My question is if can I log to the same LDAP Active Directory from the same machine in Java without need to put username and password?
Is there an equivalent to do this in Java like in C# (without put the username and password)?
If using env.put(Context.SECURITY_AUTHENTICATION, "none"); did not work, then your AD environment may not support anonymous authentication, understandably. Anonymous authentication is disabled by default.
When you use new DirectoryEntry(...); in C#, it may seem like you are not using any credentials, but it really uses the credentials of the currently logged on user. So it's borrowing your own credentials to make the call to AD.
Java does not do that. In fact, from the brief Googling I've done just now, it seems like it's quite difficult to make it do that, if that's what you want to do.
There is a question about how to do that here: Query Active Directory in Java using a logged on user on windows
The comment there gives a couple products to look into.
But if you don't want to use any third-party products that may complicate things, you can just provide a username and password.
If you want to test anonymous authentication in C#, you can use something like this:
new DirectoryEntry(ldapPath, null, null, AuthenticationTypes.Anonymous)
I found a walk around solution to create a process and run the powershell script.
In powershell script, you can use the "System.DirectoryServices" to search without passing the username and password.
Please refer Querying Active directory
I have a simple application written in C#. That application works on windows machine and user account logged to the Active Directory.
I guess your C# application simply uses a Windows API which under the hood automagically authenticates with Kerberos ticket granting ticket of your Windows session.
If you want to do that with another programming language you have to do a LDAP SASL bind request with SASL mech GSSAPI. However your e.g. Java-based LDAP client also have to make use of the Windows credentials cache.
See also: Java Platform, Standard Edition Security Developer’s Guide
I am obtaining a kerberos ticket with the following code:
String client = "com.sun.security.jgss.krb5.initiate";
LoginContext lc = new LoginContext(client, new CallbackHandler() {
#Override
public void handle(Callback[] arg0) throws IOException, UnsupportedCallbackException {
System.out.println("CB: " + arg0);
}
});
lc.login();
System.out.println("SUBJ: " + lc.getSubject());
This code works fine, I get a subject that shows my user ID. The problem I'm having is now I need to know whether the user belongs to a certain group in AD. Is there a way to do this from here?
I've seen code to get user groups using LDAP but it requires logging in with a user/password, I need to do it the SSO way.
You cannot actually do this with the kind of ticket you get at login. The problem is that the Windows PAC (which contains the group membership information) is in the encrypted part of the ticket. Only the domain controller knows how to decrypt that initial ticket.
It is possible to do with a service ticket.
So, you could set up a keytab, use jgss to authenticate to yourself and then decrypt the ticket, find the PAC, decode the PAC and then process the SIDs. I wasn't able to find code for most of that in Java, although it is available in C. Take a look at this for how to decrypt the ticket.
Now, at this point you're talking about writing or finding an NDR decoder, reading all the specs about how the PAC and sids are put together, or porting the C code to Java.
My recommendation would be to take a different approach.
Instead, use Kerberos to sign into LDAP. Find an LDAP library that supports Java SASL and you should be able to use a Kerberos ticket to log in.
If your application wants to know the groups the user belongs to in order to populate menus and stuff like that, you can just log in as the user.
However, if you're going to decide what access the user has, don't log in as the user to gain access to LDAP. The problem is that with Kerberos, an attacker can cooperate with the user to impersonate the entire infrastructure to your application unless you confirm that your ticket comes from the infrastructure.
That is, because the user knows their password, and because that's the only secret your application knows about, the user can cooperate with someone to pretend to be the LDAP server and claim to have any access they want.
Instead, your application should have its own account to use when accessing LDAP. If you do that, you can just look up the group list.
I do realize this is all kind of complex.
I am Using Google App Engine for Java and I want to be able to share session data between subdomains:
www.myapp.com
user1.myapp.com
user2.myapp.com
The reason I need this is that I need to be able to detect if the user was logged in on www.myapp.com when trying to access user1.myapp.com. I want to do this to give them admin abilities on their own subdomains as well as allow them to seamlessly switch between subdomains without having to login again.
I am willing to share all cookie data between the subdomains and this is possible using Tomcat as seen here: Share session data between 2 subdomains
Is this possible with App Engine in Java?
Update 1
I got a good tip that I could share information using a cookie with the domain set to ".myapp.com". This allows me to set something like the "current_user" to "4" and have access to that on all subdomains. Then my server code can be responsible for checking cookies if the user does not have an active session.
This still doesn't allow me to get access to the original session (which seems like it might not be possible).
My concern now is security. Should I allow a user to be authenticated purely on the fact that the cookie ("current_user" == user_id)? This seems very un-secure and I certainly hope I'm missing something.
Shared cookie is most optimal way for your case. But you cannot use it to share a session on appengine. Except the case when you have a 3rd party service to store sessions, like Redis deployed to Cloud Instances.
You also need to add some authentication to your cookie. In cryptography there is a special thing called Message Authentication Code (MAC), or most usually HMAC.
Basically you need to store user id + hash of this id and a secret key (known to both servers, but not to the user). So each time you could check if user have provided valid id, like:
String cookie = "6168165_4aee8fb290d94bf4ba382dc01873b5a6";
String[] pair = cookie.split('_');
assert pair.length == 2
String id = pair[0];
String sign = pair[1];
assert DigestUtils.md5Hex(id + "_mysecretkey").equals(sign);
Take a look also at TokenBasedRememberMeServices from Spring Security, you can use it as an example.