Google Calendar API - Getting events with Service Account stop working - google-api

Since Monday 19th of September the application we did with a service account to retrieve all events from a calendar in a different account stop working with no reason. The service account is under the company domain manage by Google for Work. We are trying to access a CalendarId managed by another user and showing them in our website using the server side library Google.Apis.Calendar.* in .Net. It continues working well since a period of almost 2 month and suddenly stop working showing this [NOT_FOUND error --> http://i.stack.imgur.com/oVAhp.png] when executes the request (request.Execute();).
public class GCalendarService : Service
{
public GCalendarService(X509Certificate2 certificate, String accountName, String appName) : base(certificate, accountName, appName)
{
this.Scopes = new string[] {
CalendarService.Scope.Calendar, // Manage your calendars
CalendarService.Scope.CalendarReadonly // View your Calendars
};
this.InitializeCredential(certificate);
this.GService = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = this.Credential,
ApplicationName = appName
});
}
/// <summary>
/// Gets all the events based on a period of time
/// </summary>
/// <param name="calendarId">The ID of the calendar to get the events</param>
/// <param name="minDate">The minimun date to request</param>
/// <param name="maxDate">The max date to request</param>
/// <param name="maxEvents">The max amount of events to retrieve</param>
/// <returns></returns>
public Object GetEvents(string calendarId, DateTime? minDate, DateTime? maxDate, int maxEvents)
{
EventsResource.ListRequest request = ((CalendarService)this.GService).Events.List(calendarId);
request.TimeMin = DateTime.Now;
request.ShowDeleted = false;
request.SingleEvents = true;
request.MaxResults = maxEvents;
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
return request.Execute();
}
Is there some update in the Apps Admin for Domain accounts, some new configuration made from google side? because we haven't change anything.
This are some of the test we did:
Create new calendar with the company email(make this with different company emails) and share calendar with the "view details" permission with the service account --> DIDNT WORK
Create new calendar with my personal gmail account and share calendar with the "view details" permission with the service account --> WORKED
Share primary or default account calendar with the "view details" permission with the service account --> WORKED
IMPORTANT: THE CALENDAR ID IS CORRECT, THE SERVICE ACCOUNT CREDENTIALS AND THE CALENDAR PERMISSION ARE TOO, I already prove it and it does get events form the calendar id in the 'TRY IT' helper google have in the developer site to access their endpoint. also any account in the domain has access to this calendar. Here is a screenshot of the calendar share settings. Also we have tried creating new service accounts and new calendars.
If someone have any idea I would be so much grateful, I would really appreciate it, I have done a lot of research a nothing seems to be the solution.

Related

Unable to fix veracode cwe id 918 flaw (SSRF) when using API gateway pattern in a Microservices architecture

I am using API Gateway Pattern in a Micro services architecture in which the Front End Angular app makes an HTTP request to my API Gateway project which is simply a ASP.net Core 3.1 Web API project. Currently I only have 2 micro services and an API Gateway and all of them are of type ASP.net Core 3.1 Web API project. The API Gateway project has all the controllers of my micro services. The purpose of the API Gateway is just to receive the request from Front end and make an HTTP Request to the appropriate Micro service.
Now in the AccountController.cs of my API Gateway project, I have the following code
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
_uri = new Uri(uriString: $"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
using var result = await _client.GetAsync(_uri);
var content = await result.Content.ReadAsStringAsync();
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
After searching about the SSRF issue on stackoverflow I found the following recommendation at Veracode community.
Veracode Static Analysis will report a flaw with CWE 918 if it can
detect that data from outside of the application (like an HTTP Request
from a user, but also a file that may have been uploaded by a user,
database data, webservice data, etc) is able to change the nature of a
network request.
On Stackoverflow I found the following fix
For CWE ID 918 it is hard to make Veracode recognize your fix unless you have static URL. You need to validate all your inputs that become parts of your request URL.
That means I had to sanitize my input parameters OrganizationId and AccountId before appending them to the request URL.
Also another question on the veracode community suggested
The only thing that Veracode Static Analysis will automatically detect as a remediation for this flaw category is to change the input to be hardcoded
and they proposed a solution for the query string
The given example appears to take a model identifier and put it in the
URL used in an internal request. We would recommend validating the ID
per the rules you have for this datatype (typically this should only
be alphanumeric and less than 255 characters) and URLencode it before
appending it to a URL.
After all those stuff, I have made the following changes to my code
Made sure OrganizationId and AccountId Guid are not empty
URL Encoded the string
Here is the code after changes
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
if (organizationId != Guid.Empty && accountId != Guid.Empty)
{
string url = HttpUtility.UrlEncode($"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
using var result = await _client.GetAsync(url);
var content = await result.Content.ReadAsStringAsync();
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
return BadRequest();
}
Thats All I could do to sanitize my input parameters OrganizationId and AccountId but after all those changes veracode still identifies a SSRF flaw on line
using var result = await _client.GetAsync(url);
I found a hack to fix this issue, I just appended the query string parameters to the Base Address of httpClient and veracode stopped giving me error.
Here is how the solution looks like
/// <summary>
/// Gets the detail of an account by its id
/// </summary>
/// <param name="organizationId">Id of the Organization of which the account belongs to</param>
/// <param name="accountId">Id of Account of which information is being requested</param>
/// <returns>Account's Details</returns>
[HttpGet("{organizationId}/{accountId}")]
public async Task<IActionResult> GetAccountAsync(Guid organizationId, Guid accountId)
{
if (organizationId != Guid.Empty && accountId != Guid.Empty)
{
var httpClient = new HttpClient();
//Appended the parameters in base address to
//to fix veracode flaw issue
httpClient.BaseAddress = new Uri($"{_configurationService.AccountAPI}GetAccount/{organizationId}/{accountId}");
//passing empty string in GetStringAsync to make sure
//veracode doesn't treat it like modifying url
var content = await httpClient.GetStringAsync("");
return Ok(content.AsObject<MessageResponse<AccountDetailVM>>());
}
return BadRequest();
}

OAuth 2.0 token based Authentication Question on storing token values

I have implemented the OAuth token-based Authentication in our WebAPI Application and validating the username and password against the database. But we are not syncing the access tokens and refresh tokens to any type of database. Here is the code, however, I have one question where the token values are stored.
Below code for generating the Token
/// <summary>
/// Grant resource owner credentials overload method.
/// </summary>
/// <param name="context">Context parameter</param>
/// <returns>Returns when task is completed</returns>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Initialization.
var usernameVal = context.UserName;
var passwordVal = context.Password;
var user = _securityLogic.AuthenticateApiUser(usernameVal, passwordVal);
// Verification.
if (!user)
{
// Settings.
context.SetError("invalid_grant", "The user name or password is incorrect.");
// Return info.
return;
}
// Initialization.
var claims = new List<Claim>
{
//var userInfo = user.FirstOrDefault();
// Setting
new Claim(ClaimTypes.Name, usernameVal)
};
// Setting Claim Identities for OAUTH 2 protocol.
ClaimsIdentity oAuthClaimIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesClaimIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
// Setting user authentication.
AuthenticationProperties properties = CreateProperties(usernameVal);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthClaimIdentity, properties);
// Grant access to authorize user.
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesClaimIdentity);
}
#endregion
#region Token endpoint override method.
/// <summary>
/// Token endpoint override method
/// </summary>
/// <param name="context">Context parameter</param>
/// <returns>Returns when task is completed</returns>
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
// Adding.
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
// Return info.
return Task.FromResult<object>(null);
}
#endregion
This is code is for generating the refresh Token
#region GrantRefreshToken
private static readonly ConcurrentDictionary<string, AuthenticationTicket> RefreshTokens =
new ConcurrentDictionary<string, AuthenticationTicket>();
/// <summary>
/// Grants Refresh Token
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
// Change authentication ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
// newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
// Copy claims from the previous token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddMinutes(30)
};
var refreshTokenTicket = await Task.Run(() =>
new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties));
RefreshTokens.TryAdd(guid, refreshTokenTicket);
// Consider storing only the hash of the handle
context.SetToken(guid);
}
#endregion
So, my question is .NET/Owin/IdentityServer3 writing them to some in-memory database? If so, can they be accessed for things like viewing and deleting? And what happens if the app server is restarted, are all the tokens wiped out? Or are they persistent?
And do you recommend storing in the Database and retrieving it from the database? Any help is appreciated, by the way, this code is working perfectly fine.
From the documentation:
If not specifically configured we will always provide an in-memory
version stores for authorization codes, consent, reference and refresh
tokens.
Please note that they talk about reference tokens and refresh tokens. JWT access tokens and identity tokens are not stored.
In order to use a refresh token in IdentityServer3 (and also IdentityServer4) it has to match a stored token.
The major benefit of that is that you can control the token. You can revoke it (removes it from the store), and define how to use it: OneTime or ReUse.
I'm not familiar with IdentityServer3, but you can take a look at github and search the code where the RevocationEndpoint is implemented, the place where the refresh token is removed from the store. That may give a clue on how to access and use the store.
With in-memory stores, the tokens are lost on restart of the IdentityServer. So persisting them in a persistent store, like a database, is a good thing for production servers. For IdentityServer4 you can implement an operational store.
Please note that JWT tokens remain valid regardless a restart of the server, unless the private key is also not persisted. In that case IdentityServer can't validate the token and has no choice but to consider the JWT tokens as invalid.
So for production environments you should persist keys and data, and using a database is fine. As you can see in IdentityServer4 there is support for this.
Speaking of IdentityServer4, since (free) support of IdentityServer3 has ended I would recommend to switch to IdentityServer4, if you are in the position to do so. Since both versions implement oidc/auth2 you should be able to keep using the clients with the upgraded IdentityServer. On stackoverflow there are questions that can help you with that. And take a look at the IdentityServer4 documentation, it's very informative.

How to Automate SharePoint Record Updates

So we have an inventory list with details about each item, mostly drop down menus and check boxes, some comments and descriptions. These records are in SharePoint. Sometimes we need to update multiple items in there in addition to a large number of other steps and I am trying to automate most of these steps including the updates to their SharePoint record. What is the best way to go about this in PowerShell from any remote computer?
Would I connect to the database, find the record and and update the record there? Is there an easier way? I tried finding PowerShell CLI tools for SharePoint but I don't see them available anywhere.
For example, I might want to update this field here:
I think the the best Automate update list item in SharePoint remotely is using CSOM(C# code) API.
Here is a demo about update list item for your reference:
// Starting with ClientContext, the constructor requires a URL to the
// server running SharePoint.
ClientContext context = new ClientContext("http://SiteUrl");
// Assume that the web has a list named "Announcements".
List announcementsList = context.Web.Lists.GetByTitle("Announcements");
// Assume there is a list item with ID=1.
ListItem listItem = announcementsList.GetItemById(1);
// Write a new value to the Body field of the Announcement item.
listItem["Body"] = "This is my new value!!";
listItem.Update();
context.ExecuteQuery();
For do authentication to access SharePoint, we can do as below:
/// <summary>
/// set authentication of SharePoint online
/// </summary>
/// <param name="clientContext"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static void setOnlineCredential(ClientContext clientContext,string userName,string password)
{
//set the user name and password
SecureString secureString = new SecureString();
foreach (char c in password.ToCharArray())
{
secureString.AppendChar(c);
}
clientContext.Credentials = new SharePointOnlineCredentials(userName, secureString);
}
/// <summary>
/// set authentication of SharePoint on-premise
/// </summary>
/// <param name="clientContext"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <param name="domain"></param>
public static void setClientCredential(ClientContext clientContext, string userName, string password, string domain)
{
clientContext.Credentials = new NetworkCredential(userName, password, domain);
}
If you want to use PowerShell, We can use PowerShell to use CSOM as this article talked: CSOM SharePoint PowerShell Reference and Example Codes and we just need to modify the code above to be PowerShell code

Office365 API access for all network users' calendars using c#

So my main objective is to update network user's outlook calendar from sql server data using office365 api every few minutes. I am stuck at how to get access for other user's outlook calendar? Looked at below link but didnt asnwser much...do i need azure subscription in order to do this? If someone can point me to right direction, that would be great
https://msdn.microsoft.com/en-us/office/office365/howto/common-app-authentication-tasks
I am stuck at how to get access for other user's outlook calendar?
In this case, you can consider using the application permission.
In Azure AD:
register a Web Application in your Azure AD.
add “Read and write calendars in all mail boxes” permission
generate the application secret key
In your application, call Office 365 Graph API - create events by using application token.
http://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/user_post_events
var tmgr = new ApplicationTokenManagement();
var token = tmgr.AcquireToken(Settings.ResourceUrlOfGraph);
var api = new Graph.GraphCalendarAPI(token);
JObject body = new JObject
{
{"subject", "Create from Office 365 API"},
{"start", new JObject { { "DateTime", "2016-03-09T00:00:00"}, { "TimeZone", "China Standard Time" } } },
{"end", new JObject { { "DateTime", "2016-03-10T00:00:00"}, { "TimeZone", "China Standard Time" } } },
{"isAllDay", true }
};
var task = api.CreateEventAsync(body, "user#youcompany.com");
task.Wait();
You can find the complete sample here.

how to post images using plusdoamins on google plus

I want to post/upload an image/picture to Google+ stream/wall in C# asp.net I have Google a lot but could not fine any solution so that i could post an image from my application to Google plus wall. please help. Thanks in advance
I don't have access to a Google+ domain account so cant help you test this. However I have a generated sample that might get you started
Oauth2
/// <summary>
/// Authenticate to Google Using Oauth2
/// Documentation https://developers.google.com/accounts/docs/OAuth2
/// </summary>
/// <param name="clientId">From Google Developer console https://console.developers.google.com</param>
/// <param name="clientSecret">From Google Developer console https://console.developers.google.com</param>
/// <param name="userName">The user to authorize.</param>
/// <returns>a valid PlusDomainsService</returns>
public static PlusDomainsService AuthenticateOauth(string clientId, string clientSecret, string userName)
{
if (string.IsNullOrEmpty(clientId))
throw new Exception("clientId is required.");
if (string.IsNullOrEmpty(clientSecret))
throw new Exception("clientSecret is required.");
if (string.IsNullOrEmpty(userName))
throw new Exception("userName is required for datastore.");
string[] scopes = new string[] { PlusDomainsService.Scope.PlusCirclesRead, // View your circles and the people and pages in them
PlusDomainsService.Scope.PlusCirclesWrite, // Manage your circles and add people and pages. People and pages you add to your circles will be notified. Others may see this information publicly. People you add to circles can use Hangouts with you.
PlusDomainsService.Scope.PlusLogin, // Know your basic profile info and list of people in your circles.
PlusDomainsService.Scope.PlusMe, // Know who you are on Google
PlusDomainsService.Scope.PlusMediaUpload, // Send your photos and videos to Google+
PlusDomainsService.Scope.PlusProfilesRead, // View your own Google+ profile and profiles visible to you
PlusDomainsService.Scope.PlusStreamRead, // View your Google+ posts, comments, and stream
PlusDomainsService.Scope.PlusStreamWrite, // Manage your Google+ posts, comments, and stream
PlusDomainsService.Scope.UserinfoEmail, // View your email address
PlusDomainsService.Scope.UserinfoProfile}; // View your basic profile info
try
{
string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/PlusDomains");
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId, ClientSecret = clientSecret }
, scopes
, userName
, CancellationToken.None
, new FileDataStore(credPath, true)).Result;
var service = new PlusDomainsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "PlusDomains Authentication Sample",
});
return service;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
throw ex;
}
}
Media upload
class MediaSample
{
/// <summary>
/// Add a new media item to an album. The current upload size limitations are 36MB for a photo and 1GB for a video. Uploads do not count against quota if photos are less than 2048 pixels on their longest side or videos are less than 15 minutes in length.
/// Documentation: https://developers.google.com/+/domains//v1/media/insert
/// </summary>
/// <param name="service">Valid authentcated PlusDomainsService</param>
/// <param name="body">Valid Media Body</param>
/// <param name="userId">The ID of the user to create the activity on behalf of.</param>
/// <param name="collection"> Upload the media to share on Google+.</param>
/// <returns>Media </returns>
public static Media Insert(PlusDomainsService service, Media body, string userId, MediaResource.InsertRequest.CollectionEnum collection)
{
//Note Genrate Argument Exception (https://msdn.microsoft.com/en-us/library/system.argumentexception(loband).aspx)
try
{
return service.Media.Insert(body, userId, collection).Execute();
}
catch (Exception ex)
{
Console.WriteLine("Request Failed " + ex.Message);
throw ex;
}
}
}

Resources