Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IMAP.AccessAsApp supported? #61

Closed
ft3411 opened this issue Sep 6, 2022 · 11 comments · Fixed by #65
Closed

IMAP.AccessAsApp supported? #61

ft3411 opened this issue Sep 6, 2022 · 11 comments · Fixed by #65

Comments

@ft3411
Copy link

ft3411 commented Sep 6, 2022

Is there a way to use the proxy using the new IMAP and POP.AccessAsApp Permissions in Office 365?
In this case the administrator could handle the authorization and set the permissions for the account in Office 365 and the handling of authorizations on the gateway would not be necessary.

Reference: https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth

Thank you for your answer -tom

@simonrob
Copy link
Owner

simonrob commented Sep 6, 2022

It looks from your reference and the client credentials flow reference that a few relatively minor adjustments to the authorisation flow (probably scope and grant type changes, getting new tokens without a refresh token, and adding the admin consent step) would allow this to be supported.

I don't have access to an environment to test this in, so can't add this myself. Can you provide a test setup?

Once you have obtained the token there is no difference to the standard route, so yes, you should have no problem using the proxy in this way. But the proxy would still need to obtain the token initially, so this wouldn't entirely remove having to handle client authorisations.

@ft3411
Copy link
Author

ft3411 commented Sep 6, 2022

Actually the admin consent is handled when the application is set up in Azure AD. When establishing the connection you only present the App-Id, the client secret or a certificate and the tenant-id. This creates the token which can then be used to log into the mailbox.
I can give you access to my test tenant if you want to try it.

This is what I did when I tested all this stuff:

#This is all I need to access the mailbox:
$Pwd = ConvertTo-SecureString '***************************' -AsPlainText -Force
$a = Get-MsalToken -ClientId yyyyyyyyyyyyyyyyyyyy -TenantId xxxxxxxxxxxxxxxxxxxxxx -Scope "https://outlook.office365.com/.default" -Clientsecret $Pwd
$oa = "user=brigitte@xxx.xxx.com" + [char]1 + "auth=Bearer $($a.Accesstoken)" + [char]1 + [char]1
$auth = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.Getbytes($oa))

@simonrob
Copy link
Owner

simonrob commented Sep 6, 2022

Ah, ok. In that case, you probably have all you need here to preconfigure the proxy for a particular account. I'd suggest manually creating a config file entry (see below) and trying to log in via an email client (or telnet/PuTTY etc).

From your example it looks like when using this method the client_secret value really is a secret that should be protected. This is not the case usually; only the token values are sensitive. Depending on how the AccessAsApp method works, it might be worth encrypting this value in the same way.

The following example is based on the proxy's authorisation code. If you save this as a python script and edit the values to match your configuration, running it should produce a working account section that you can paste into the proxy's configuration file. If you can get things to work this way, we can then use your test tenant to find a way to streamline setup.

Update: this setup script is superseded by the version below.

@edombroski
Copy link

Not the original poster, but I can confirm the above works. I would be very interested in getting it streamlined so the proxy itself can get the initial oauth, subsequent refresh tokens, and proper token expiration times.

By the way, in addition to registering an app in Azure, I also had to create a service principal and then assign that service principal FullAccess permissions to the mailbox.

New-ServicePrincipal -AppId CLIENT_ID -serviceid OBJECT_ID_FROM_ENTERPRISE_APPS_VIEW -Organization TENANT_ID

Add-MailboxPermission -Identity MAILBOX@DOMAIN.COM -user OBJECT_ID_FROM_ENTERPRISE_APPS_VIEW -AccessRights FullAccess

@simonrob
Copy link
Owner

simonrob commented Sep 27, 2022

This mode has now been merged into the proxy. Please carefully read the documentation, as this feature essentially means that there is no access control when using the proxy in certain environments, and it is very important to be aware of this before using it in a publicly accessible context.

O365 only supports IMAP and POP in this configuration, not SMTP. (This is no-longer the case; IMAP/POP/SMTP are all now supported).

The following script will create a pre-encrypted proxy configuration entry:

import base64
import getpass
import os

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

print('\nThis script creates a pre-encrypted Email OAuth 2.0 Proxy configuration file entry for accounts using the '
      'Office 365 client credentials grant flow.\nFor more information about configuring the AAD client itself, see: '
      'https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-sm'
      'tp-application-by-using-oauth#use-client-credentials-grant-flow-to-authenticate-smtp-imap-and-pop-connections\n')
username = input('Enter your email address: ')
password = getpass.getpass('Enter the password you will use for IMAP/POP/SMTP access: ')
tenant_id = input('Enter your Office 365 tenant ID: ')
client_id = input('Enter your AAD client ID: ')
client_secret = getpass.getpass('Enter your AAD client secret: ')

token_salt = base64.b64encode(os.urandom(16)).decode('utf-8')
token_iterations = 870_000
key_derivation_function = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                                     salt=base64.b64decode(token_salt.encode('utf-8')), iterations=token_iterations,
                                     backend=default_backend())
fernet = Fernet(base64.urlsafe_b64encode(key_derivation_function.derive(password.encode('utf-8'))))

print('\n[%s]' % username)
print('token_url = https://login.microsoftonline.com/%s/oauth2/v2.0/token' % tenant_id)
print('oauth2_scope = https://outlook.office365.com/.default')
print('oauth2_flow = client_credentials')
print('redirect_uri = http://localhost')
print('client_id =', client_id)
print('client_secret_encrypted =', fernet.encrypt(client_secret.encode('utf-8')).decode('utf-8'))
print('token_salt =', token_salt)
print('token_iterations =', token_iterations, '\n')

@edombroski
Copy link

@simonrob Thank you. It appears to work perfectly.

@sflamm
Copy link

sflamm commented Oct 20, 2022

It looks from your reference and the client credentials flow reference that a few relatively minor adjustments to the authorisation flow (probably scope and grant type changes, getting new tokens without a refresh token, and adding the admin consent step) would allow this to be supported.

I don't have access to an environment to test this in, so can't add this myself. Can you provide a test setup?

Once you have obtained the token there is no difference to the standard route, so yes, you should have no problem using the proxy in this way. But the proxy would still need to obtain the token initially, so this wouldn't entirely remove having to handle client authorisations.

@simonrob I am very happy to provide you access to our environment as much as you need ... your work is super beneficial to everyone and much appreciated. Should we coordinate directly through email?

@sflamm
Copy link

sflamm commented Oct 20, 2022

@edombroski

Looks like there is a permission with full access to email boxes without authentication

full_access_as_app

A service principal is required for Applications registered ... one instance is created per tenant

A service principal is created in every tenant where the application is used. Similar to a class in object-oriented programming, the application object has some static properties that are applied to all the created service principals (or application instances).

But do you need to assign the principal permission on individual mailboxes if you have already used the "full_access_as_app" permission.... doing so makes this hard to maintain as you would have to always do it for new accounts... Am I correct the "full_access_as_app" makes this unnecessary? (I am yet to set up a working proxy yet... trying to understand what is required for a proper configuration)

As you have pointed out above - and it is a subtly easily overlooked... when getting the Application ID:

The OBJECT_ID is the Object ID **from the Overview page of the Enterprise Application node (Azure Portal) for the application** registration. It is not the Object ID from the Overview of the App Registrations node. Using the incorrect Object ID will cause an authentication failure.

@sflamm
Copy link

sflamm commented Oct 20, 2022

@simonrob

You make a point that the Flow permissions do not include SMTP ... only POP and IMAP

request -api-permissions

Also useful to note - Microsoft has no plans to change the basic authentication on SMTP. They claim this will remain unaffected

scope-of-disablement

For folks like myself - which are trying to find a way to have their Microsoft CRM Dynamics 2016 continue to work with POP/SMTP... we can create a profile that directs POP traffic to the proxy and SMTP traffic straight to outlook.office365.com as always... the key is the most maintanable/non-fragile solution possible

Can you recommend/detail out the configuration to meet these needs?

@edombroski
Copy link

@sflamm

I believe full_access_as_app only covers Exchange Web Services which is entirely distinct (HTTP based) protocol from IMAP. I do not think that would work, but I have not tested it.

@sflamm
Copy link

sflamm commented Oct 22, 2022

@edombroski

From here: https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access

Looks like you are correct:
`Administrators can use ApplicationAccessPolicy cmdlets to control mailbox access of an app that has been granted any of the following Microsoft Graph application permissions or Exchange Web Services permissions.

Microsoft Graph application permissions:

Mail.Read
Mail.ReadBasic
Mail.ReadBasic.All
Mail.ReadWrite
Mail.Send
MailboxSettings.Read
MailboxSettings.ReadWrite
Calendars.Read
Calendars.ReadWrite
Contacts.Read
Contacts.ReadWrite
Exchange Web Services permission scope: full_access_as_app.`

With that said - we should be able to make use of this - which allows us to create a Mail Enabled Group of only the users we want the principal to have access to... this requires us to only add/remove users from the group and not change the principals ongoing permissions:

There are scenarios where administrators may want to limit an app to only specific mailboxes and not all Exchange Online mailboxes in the organization. **Administrators can identify the set of mailboxes to permit access by putting them in a mail-enabled security group. Administrators can then limit third-party app access to only that set of mailboxes by creating an application access policy for access to that group**.

Can you add this to your script? (creating a mail enabled security group and assigning the principal permissions to it)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants