Gmail API returns "Delegation denied for my.email#email.com" when setting a different email as recipient - kotlin

I am using the Gmail API to create drafts.
When I create a draft message where the recipient is my own email (the one that generates the credential), everything works fine but when I try to use a different email, the following message is printed:
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Delegation denied for my.email.here#gmail.com",
"reason" : "forbidden"
} ],
"message" : "Delegation denied for my.email.here#gmail.com",
"status" : "PERMISSION_DENIED"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
That's how I'm assembling my MimeMessage:
val props = Properties()
val session = Session.getDefaultInstance(props, null)
val message = MimeMessage(session)
message.setFrom(message.sender)
message.addRecipient(JavaxMessage.RecipientType.TO, InternetAddress("different.email.here#gmail.com"))
message.subject = subject
The scopes I am using:
// "https://www.googleapis.com/auth/gmail.compose"
GmailScopes.GMAIL_COMPOSE
I've tried a lot of stuff to make it work, but I didn't have any success.

Delegation denied for my.email.here#gmail.com
This email appears to be a standard gmail email address. You appear to be trying to delegate a service account to a user with a standard gmail email address.
Service accounts only work with google workspace email accounts. You need to set up domain wide delegation to the serveries account and then you can set the delegation user.
If you want to use a standard gmail account you will need to authorize the user using Oauth2.
private fun getCredentials(httpTransport: NetHttpTransport): Credential? {
val inputStream = File("credentials.json").inputStream()
val clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
val flow = GoogleAuthorizationCodeFlow.Builder(httpTransport, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build()
val receiver = LocalServerReceiver.Builder().setPort(8888).build()
return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
}
public static MimeMessage createEmail(String to,
String from,
String subject,
String bodyText)
throws MessagingException {
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
MimeMessage email = new MimeMessage(session);
email.setFrom(new InternetAddress(from));
email.addRecipient(javax.mail.Message.RecipientType.TO,
new InternetAddress(to));
email.setSubject(subject);
email.setText(bodyText);
return email;
}
Sending

Thank you #DaImTo for your reply and all the info provided.
Based on the info provided, to those who have the same problem as I do, I've found a nice workaround:
set the "TO" recipient to the user's email;
add a "BCC" recipient to the actual recipient email.
Here's the code to demonstrate it:
val meProfile = gmail.users().getProfile("me").execute()
val toRecipient = InternetAddress(meProfile.emailAddress)
val ccRecipient = InternetAddress(to)
val props = Properties()
val session = Session.getDefaultInstance(props, null)
val message = MimeMessage(session)
message.setFrom(message.sender)
// here's the magic :D
message.addRecipient(JavaxMessage.RecipientType.TO, toRecipient)
message.addRecipient(JavaxMessage.RecipientType.BCC, ccRecipient)
message.subject = subject

Related

Getting ID token from google sign-in

I am trying to use http4k's built in oauth module to implement Google sign-in in my backend app.
Here is what I have so far:
val googleClientId = "<GoogleClientID>"
val googleClientSecret = "<GoogleClientSecret>"
// this is a test implementation of the OAuthPersistence interface, which should be
// implemented by application developers
val oAuthPersistence = InsecureCookieBasedOAuthPersistence("Google")
// pre-defined configuration exist for common OAuth providers
val oauthProvider = OAuthProvider.google(
JavaHttpClient(),
Credentials(googleClientId, googleClientSecret),
Uri.of("http://localhost:9000/oauth/callback"),
oAuthPersistence
)
val app: HttpHandler = routes(
"/oauth" bind routes(
"/" bind GET to oauthProvider.authFilter.then {
val user = contextFn(it)
Response(OK).body("authenticated!")
},
"/callback" bind GET to oauthProvider.callback
)
app.asServer(SunHttp(9000)).start()
This lets me go to http://localhost:9000/oauth and I can sign-in to my google account. Cool!
However, after the redirect, I go to the following function contextFn, which looks like this atm:
val transport = NetHttpTransport()
val jsonFactory = GsonFactory.getDefaultInstance()
val verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(listOf(googleClientId))
.build()
fun contextFn(request: Request): Principal {
// TODO: get the id token somehow, but the request header only contains the following in cookie:
// - GoogleCsrf
// - GoogleAccessToken
// - GoogleOriginalUri
val idTokenString = ""
val idToken: GoogleIdToken = verifier.verify(idTokenString)
val payload: GoogleIdToken.Payload = idToken.payload
// Print user identifier
val userId: String = payload.subject
println("User ID: $userId")
// Get profile information from payload
val email: String = payload.email
val emailVerified: Boolean = payload.emailVerified
val name = payload["name"]
return GoogleUser(email)
}
How can i get the id token? Currently I am getting the access token from google.
Can you try to add this scope(openid)?
I am not sure listOf or addScope support in http4k
But it missed openid scope.
val oauthProvider = OAuthProvider.google(
JavaHttpClient(),
Credentials(googleClientId, googleClientSecret),
Uri.of("http://localhost:9000/oauth/callback"),
listOf("openidScope"),
oAuthPersistence
)
oauthProvider.addScope('openid');

Request a MS Graph Access token from Middleware

I'm trying to request MS Graph API from an API Middleware, in ASP.Net, to create programmatically Office Planners on specific api calls.
Customers are logged in to the frontend application via Azure AD SSO (using adal).
This access token allow users to authenticate to my api.
Now, i want to request a MS Graph access token from this API access token, to create the planners from the authenticated user account (on behalf of).
How can i proceed ?
Could the OBO flow (On-Behalf-Of flow) work in this case ?
What Tiny Wang has said is correct, you could do that in Program.cs then use dependency injection for the Graph client inside your controller.
Here's a quick example below of posting an event to the authenticated users calendar.
namespace MyApp.Controllers;
[Authorize][AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public class HomeController: Controller {
private readonly GraphServiceClient _graphClient;
public HomeController(GraphServiceClient graphClient) {
_graphClient = graphClient;
}
public async Task AddUserCalendarEvent() {
var #event = new Event {
Subject = "Testing",
Body = new ItemBody {
ContentType = BodyType.Html,
Content = "Test Body"
},
Start = new DateTimeTimeZone {
DateTime = "2022-02-18T12:00:00",
TimeZone = "Europe/London"
},
End = new DateTimeTimeZone {
DateTime = "2022-02-18T12:00:00",
TimeZone = "Europe/London"
}
};
await _graphClient.Me.Events.Request().AddAsync(#event);
}
}

Change auth credentials in KTor client

How can I change the credentials in a KTor client?
The Auth feature needs to be installed when the client is created. I've tried doing it later but it seems not to work, either as a first time setup or a repeat.
The docs suggest holding onto the client once it's created as the setup is expensive, so it seems unduly restrictive not to be able to change the credentials (& surely the smart folks at JetBrains wouldn't have done that).
I have an answer which works, to my surprise, but I'm not sure it's a good answer. Comments welcome.
Because the docs say that creating the client is expensive I've put it in a singleton and then I've done something like this
#ThreadLocal
Object ServerLink {
fun setClient(id:String, pw:String) {
// Create the client here and set id and pw
}
}
Then I simply call ServerLink.setClient(newId, newPW) whenever I want. Yes this works, and I didn't think I'd got multiple threads, but won't this be a memory leak, or at least memory waste?
You can do it by getting a reference to Auth feature and mutating its list of providers. Here is an example of changing Basic authentication credentials after client creation:
val client = HttpClient(CIO) {
install(Auth) {
basic {
username = "user"
password = "password"
}
}
}
val auth = client.feature(Auth)
if (auth != null) {
auth.providers.removeAt(0)
auth.basic {
username = "new-user"
password = "new-password"
}
}
val r = client.get<String>("http://httpbin.org/basic-auth/new-user/new-password")
println(r)
With ktor version 2.2.1 I managed to update the bearer token credentials like this:
const val REFRESH_TOKEN = "" // not required
val client = HttpClient(CIO) {
Auth {
bearer {
sendWithoutRequest { true }
loadTokens { BearerTokens(initialToken, REFRESH_TOKEN) }
}
}
}
fun updateBearerCredentials(newToken: String) {
client.plugin(Auth).bearer {
loadTokens { BearerTokens(newToken, REFRESH_TOKEN) }
}
}
I assume the same works for basic auth. Have not tested it though.

Asp.net core identity change username/email

The default identity change username/email with confirmation logic doesn't make sense.
Set app with require email confirmation.
Set require confirmed email to sign in.
User then changes email, enters email incorrectly, logs out.
Now the user is locked out. Email has changed but requires
confirmation to sign in and no email confirmation link because
address entered incorrectly.
Have I setup my application wrong or did Microsoft not design Identity very well?
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
//...
var email = await _userManager.GetEmailAsync(user);
if (Input.Email != email)
{
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
StatusMessage = "<strong>Verify your new email</strong><br/><br/>" +
"We sent an email to " + Input.Email +
" to verify your address. Please click the link in that email to continue.";
}
//...
await _signInManager.RefreshSignInAsync(user);
return RedirectToPage();
}
Your issue is using SetEmailAsync for this purpose. That method is intended to set an email for a user when none exists currently. In such a case, setting confirmed to false makes sense and wouldn't cause any problems.
There's another method, ChangeEmailAsync, which is what you should be using. This method requires a token, which would be obtained from the email confirmation flow. In other words, the steps you should are:
User submits form with new email to change to
You send a confirmation email to the user. The email address the user is changing to will need to be persisted either in the confirmation link or in a separate place in your database. In other words, the user's actual email in their user record has not changed.
User clicks confirmation link in email. You get the new email address they want to change to either from the link or wherever you persisted it previously
You call ChangeEmailAsync with this email and the token from from the confirmation link.
User's email is now changed and confirmed.
EDIT
FWIW, yes, this appears to be an issue with the default template. Not sure why they did it this way, since yes, it very much breaks things, and like I said in my answer, ChangeEmailAsync exists for this very purpose. Just follow the steps I outlined above and change the logic here for what happens when the user submits a new email address via the Manage page.
EDIT #2
I've filed an issue on Github for this. I can't devote any more time to it at the moment, but I'll try to submit a pull request for a fix if I have time and no one else beats me to it. The fix is relatively straight-forward.
EDIT #3
I was able to get a basic email change flow working in a fork. However, the team has already assigned out the issue and seem to be including it as part of a larger overhaul of the Identity UI. I likely won't devote any more time to this now, but encourage you to follow the issue for updates from the team. If you do happen to borrow from my code to implement a fix now, be advised that I was attempting to create a solution with a minimal amount of entropy to other code. In a real production app, you should persist the new email somewhere like in the database instead of passing it around in the URL, for example.
As already identified, the template definitely provides the wrong behaviour. You can see the source for the template in the https://github.com/aspnet/Scaffolding repo here.
I suggest raising an issue on the GitHub project so this is changed. When the templates are updated, they'll no doubt have to account for both the case when confirmation is enabled and when it's not. In your case, you can reuse the logic that already exists in OnPostSendVerificationEmailAsync() relatively easily.
A more general implementation would look something like this:
public partial class IndexModel : PageModel
{
// inject as IOptions<IdentityOptions> into constructor
private readonly IdentityOptions _options;
// Extracted from OnPostSendVerificationEmailAsync()
private async Task SendConfirmationEmail(IdentityUser user, string email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
}
public async Task<IActionResult> OnPostAsync()
{
//... Existing code
var email = await _userManager.GetEmailAsync(user);
var confirmationEmailSent = false;
if (Input.Email != email)
{
if(_options.SignIn.RequireConfirmedEmail)
{
// new implementation
await SendConfirmationEmail(user, Input.Email);
confirmationEmailSent = true;
}
else
{
// current implementation
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
// existing update phone number code;
await _signInManager.RefreshSignInAsync(user);
StatusMessage = confirmationEmailSent
? "Verification email sent. Please check your email."
: "Your profile has been updated";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var email = await _userManager.GetEmailAsync(user);
await SendConfirmationEmail(user, email);
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}

Updating Roles when granting Refresh Token in Web Api 2

I have developed an authentication mechanism in Asp.Net Web Api 2 with the feature for granting refresh tokens, based on the tutorial on Taiseer's blog.
Here is my question. Assume the following scenario:
A user logs in using password and get a refresh token and an access token. The access token in fact includes what roles he is in (hence his authorities within the app). In the mean time the system admin will change this person's roles, so once his access token expires and he wants to use the refresh token to obtain a new access token, his new access token must include the newly updated roles for him.
In my "RefreshTokenProvider" class, I am using the following code in "GrantResourceOwnerCredentials" method to get the user roles from the database and add them to the claims:
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
var y = roleManager.Roles.ToList();
var id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
id.AddClaim(new Claim("sub", context.UserName));
var roles2 = UserRoleManagerProvider.RoleManager().Roles.ToList();
foreach (IdentityRole i in roles2)
{
if (roleIds.Contains(i.Id))
id.AddClaim(new Claim(ClaimTypes.Role, i.Name));
}
This piece works fine (even though I believe there should be a nicer way to do it?!)
But the part that is not working properly is in the "GrantRefreshToken" method, where we need to update roles in order to reflect them in the new access token:
var newId = new ClaimsIdentity(context.Ticket.Identity);
// *** Add shit here....
var userId = context.Ticket.Properties.Dictionary["userId"];
IdentityUser user = UserRoleManagerProvider.UserManager().FindById(userId);
foreach (Claim c in newId.Claims)
{
if (c.Type == ClaimTypes.Role) newId.RemoveClaim(c);
}
if (user.Roles.Count > 0)
{
var roleIds = new List<string>();
var roles2 = UserRoleManagerProvider.RoleManager().Roles.ToList();
foreach (IdentityUserRole ir in user.Roles)
{
roleIds.Add(ir.RoleId);
}
foreach (IdentityRole r in roles2)
{
if (roleIds.Contains(r.Id))
newId.AddClaim(new Claim(ClaimTypes.Role, r.Name));
}
}
Again, if there is a nicer way to do it I'd appreciate you guy's help! But mainly, my problem is that the part for removing the Roles that are not in effect anymore, does not work.
Do you by any chance know what is wrong with that piece?!
FYI, in the above code the "UserRoleManagerProvider" is a simple static class I have created which is like this:
public static class UserRoleManagerProvider
{
public static RoleManager<IdentityRole> RoleManager()
{
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
return roleManager;
}
public static UserManager<IdentityUser> UserManager()
{
var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(new ApplicationDbContext()));
return userManager;
}
}
It is difficult to answer this question, and since there is a lot that needs to be included, I've tried to seperate some issues.
Claims
There are two ways to add claims to the ClaimsIdentity.
Persist claims in the store (in the database the tables AspNetUserClaims, AspNetRoleClaims). To add claims use UserManager.AddClaim or RoleManager.AddClaim. Roles (AspNetUserRoles) are special, since they are also counted as claims.
Add claims in code. You can add claims from the ApplicationUser class (usefull for extended properties of the IdentityUser) or in the flow.
Please note the difference! While in all cases it is called AddClaim, the first variant adds the claims to the store, while the second variant adds the claims directly to the ClaimsIdentity.
So how are persisted claims added to the ClaimsIdentity? This is done automatically!
As a side note, you can extend the IdentityUser with properties, but you can also add user claims to the store. In both cases the claim will be added to the ClaimsIdentity. The extended property has to be added in ApplicationUser.GenerateUserIdentityAsync:
public class ApplicationUser : IdentityUser
{
public string DisplayName { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("DisplayName", DisplayName));
return userIdentity;
}
}
Flow
Before issuing a new access_token the server must validate the user. There may be reasons why the server cannot issue a new access_token. Also the changed configuration has to be taken into account. There are two providers for this setup. The access_token provider and the refresh_token provider.
When a clients makes a request to the token endpoint (grant_type = *), AccessTokenProvider.ValidateClientAuthentication is executed first. If you are using client_credentials then you can do something here. But for the current flow we assume context.Validated();
The provider supports various flows. You can read about it here: https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx
The provider is built as opt-in. If you do not override the certain methods, then access is denied.
Access Token
To obtain an access token, credentials have to be sent. For this example I will assume 'grant_type = password'. In AccessTokenProvider.GrantResourceOwnerCredentials the credentials are checked, the ClaimsIdentity is setup and a token is issued.
In order to add a refresh_token to the ticket we need to override AccessTokenProvider.GrantRefreshToken. Here you have two options: reject the token. Because the refresh_token was revoked or for another reason why the user isn't allowed to use the refresh token anymore. Or setup a new ClaimsIdentity to genereate a new access_token for the ticket.
class AccessTokenProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Reject token: context.Rejected(); Or:
// chance to change authentication ticket for refresh token requests
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var appUser = await userManager.FindByNameAsync(context.Ticket.Identity.Name);
var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
var newTicket = new AuthenticationTicket(oAuthIdentity, context.Ticket.Properties);
context.Validated(newTicket);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var appUser = await userManager.FindAsync(context.UserName, context.Password);
if (appUser == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var propertyDictionary = new Dictionary<string, string> { { "userName", appUser.UserName } };
var properties = new AuthenticationProperties(propertyDictionary);
var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
// Token is validated.
context.Validated(ticket);
}
}
If the context has a validated ticket, the RefreshTokenProvider is called. In the Create method you can set the expiration time and choose to add the refresh token to the ticket. Do not issue new tokens while the current one isn't expired yet. Otherwise the user may never have to login again!
You can always add the refresh_token if it is somehow persisted. Or you can add a new refresh_token on login only. The user is identified so the 'old' refresh_token doesn't matter anymore since it will expire before the new refresh_token does. If you want to use one active refesh_token only, then you'll have to persist it.
class RefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
var form = context.Request.ReadFormAsync().Result;
var grantType = form.GetValues("grant_type");
// do not issue a new refresh_token if a refresh_token was used.
if (grantType[0] != "refresh_token")
{
// 35 days.
int expire = 35 * 24 * 60 * 60;
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
// Add the refresh_token to the ticket.
context.SetToken(context.SerializeTicket());
}
base.Create(context);
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
base.Receive(context);
}
}
This is just a simple implementation of the refresh_token flow and not complete nor tested. It is just to give you some ideas on implementing the refresh_token flow. As you can see it isn't hard to add claims to the ClaimsIdentity. I didn't add code where persisted claims are maintained. All what matters is that the persisted claims are automatically added!
Please notice that I reset the ClaimsIdentity (new ticket) on refreshing the access_token using the refresh_token. This will create a new ClaimsIdentity with the current state of claims.
I will end with one final remark. I was talking about roles being claims. You may expect that User.IsInRole checks the AspNetUserRoles table. But it doesn't. As roles are claims it checks the claims collection for available roles.