using google service account to create public calendar - google-api

I have been trying to use a google service account to create a public calendar and then insert some events to it.
After making sure that the events are public ('visibility': 'public') I still couldn't access them. I then read that the calendar itself has to be public but in the google documentation there is no parameter for the insert function of a new calendar that allows creating it as a public calendar (docs).
Here is the small util class I wrote for this:
import json
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from google.oauth2 import service_account
class CalendarUtils:
SCOPES = ['https://www.googleapis.com/auth/calendar']
SERVICE_ACCOUNT_FILE = 'token.json'
CAL_MAPPING_JSON_PATH = 'calendar_mapping.json'
def __init__(self):
creds = service_account.Credentials.from_service_account_file(
self.SERVICE_ACCOUNT_FILE, scopes=self.SCOPES)
self.service = build('calendar', 'v3', credentials=creds)
self.mapping = json.load(open(self.CAL_MAPPING_JSON_PATH))
def list_all_calendars(self):
res = self.service.calendarList().list().execute()
return res['items']
def create_new_calendar(self, location_name):
calendar = {
'summary': location_name,
'timeZone': 'America/Los_Angeles',
'visibility': 'public',
}
return self.service.calendars().insert(body=calendar).execute()
def insert_event(self, location_name, event):
exiting_calendars = [i['name'] for i in self.mapping['map']]
if location_name not in exiting_calendars:
calendar_id = self.create_new_calendar(location_name)['id']
self.mapping['map'].append({'name': location_name, 'calendar_id': calendar_id})
json.dump(self.mapping, open(self.CAL_MAPPING_JSON_PATH, 'w'))
else:
calendar_id = [i for i in self.mapping['map'] if i['name'] == location_name][0]['calendar_id']
event = self.service.events().insert(calendarId=calendar_id, body=event).execute()
print(f'Event created: {event.get("htmlLink")}')
if __name__ == '__main__':
cal_util = CalendarUtils()
event = {
'summary': 'Google I/O 2015',
'location': '800 Howard St., San Francisco, CA 94103',
'description': 'A chance to hear more about Google\'s developer products.',
'start': {
'dateTime': '2015-05-28T09:00:00-07:00',
'timeZone': 'America/Los_Angeles',
},
'end': {
'dateTime': '2015-05-28T17:00:00-07:00',
'timeZone': 'America/Los_Angeles',
},
'visibility': 'public',
'privateCopy': False,
'locked': False,
'anyoneCanAddSelf': True,
}
cal_util.insert_event('test', event)
This creates the calendar and insert the event, but it is not public.

To make a calendar public, you need to set scope type of the calendar Acl to default.
Example:
rules = {
"role": "reader",
"scope": {
"type": "default",
}
}
created_rule = service.acl().insert(calendarId='Insert calendar id here', body=rules).execute()
Here I created an event inside the calendar created using service account and where the Acl is applied
eventDetails = {
'summary': '67407093 test event',
'location': 'Dummy',
'description': 'Dummy',
'start': {
'dateTime': '2021-05-28T09:00:00-07:00',
'timeZone': 'America/Los_Angeles',
},
'end': {
'dateTime': '2021-05-28T17:00:00-07:00',
'timeZone': 'America/Los_Angeles',
}
}
event = service.events().insert(calendarId='Insert calendar id here', body=eventDetails).execute()
Output:
Reference:
Calendar API - Acl insert method

Related

How to access identity provider (idp) claim from .net Core web API in IdentityServer4?

In my .Net Core web API protected by IdentityServer4, I need to decide what identity provider (Google, Windows, or local, for instance) authenticated the user. So far, I am not sure how to do that.
If I search for idp claim from access_token in a controller, as shown below, I can see the claim value correctly
var accessToken = await HttpContext.GetTokenAsync("access_token");
var token = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var claim = token.Claims.First(c => c.Type == "idp").Value;
But if I try to find it using AuthorizationHandlerContext in a non-controller class in API as following, as shown in code below, it is not there
var identity = context.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
// var v = identity.FindFirst("idp").Value;
}
So looks like that idp is indeed in the token, it just not accessible from the non-controller class where it is needed. How do I get idp from non-controller class in API?
UPDATE - 1
Here is my ConfigureService in my API
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true; // test only
services.AddControllers();
services.AddControllers()
.AddNewtonsoftJson(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
});
services.Configure<QLHostOptions>(Configuration.GetSection(QLHostOptions.Host));
services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>
{
options.Authority = Configuration.GetSection(QLHostOptions.Host).Get<QLHostOptions>().IdentityGateway;
options.SaveToken = true;
// test only
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
}).AddOpenIdConnect(options =>
{
options.ClaimActions.Remove("aud");
});
services.AddTransient<IAuthorizationPolicyProvider, QLPolicyProvider>();
services.AddTransient<IAuthorizationHandler, QLPermissionHandler>();
services.AddTransient<gRPCServiceHelper>();
}
UPDATE-2
Changed ...Remove("idp") to inside AddJwtBearer, as Tory suggested, but it doesn't take it (see screenshot below):
and here is the access token from API
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjBFM0Y2MkRGMTdFQUExQURFRTc1NDQzQzQ0M0YxRkU2IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NDAwMzExMDcsImV4cCI6MTY0MDAzNDcwNywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwNSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjYwMDUvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoibXZjIiwic3ViIjoiZGM0YWI1OGMtNGVjMC00ZTAyLWIxM2YtYzEyYzk1MzJlNzcyIiwiYXV0aF90aW1lIjoxNjQwMDMxMTA2LCJpZHAiOiJHb29nbGUiLCJBc3BOZXQuSWRlbnRpdHkuU2VjdXJpdHlTdGFtcCI6IjJEQ0hXNVRER1E3NDNSUEpOWE43SVJIWlRIVllIUTRJIiwibmFtZSI6IkxpZmVuZyBYdSIsImVtYWlsIjoibGlmZW5neHUyNkBnbWFpbC5jb20iLCJyb2xlIjoiUUxBZG1pbiIsInByZWZlcnJlZF91c2VybmFtZSI6IjM1ZGJkMmY2LTlmNDUtNDJhYy04M2EzLTgzZmUyMTFjNTNiNSIsIklzRW5hYmxlZCI6IlRydWUiLCJRaWNMaW5rVUlEIjoiIiwianRpIjoiQzE3Qjc2QzQ0NjA4MzkxMDBENEExMEM4Q0YwQzA1NDEiLCJzaWQiOiIzMEY5NTA5NzQ3OUUxMzAyMUVBQTdDOTAzNzg4MDcxNiIsImlhdCI6MTY0MDAzMTEwNywic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiUWljTGlua0NJRCIsIlFpY0xpbmtVSUQiLCJyb2xlcyIsIklzRW5hYmxlZCIsIkxpZmVuZ0FQSSIsIlFpY0xpbmtBUEkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsiZXh0ZXJuYWwiXX0.boZCqYImWfkE48X5UgFOAAz9bR6CH2cwAYHGd4Ykg0vDH9qnYdje5Zmqov4HpINsu_rt16zxAX_JCEn0hvdznXK2NQyZSBGsjF0tcMgtOY0__kAfhpOT-fORakiIjeMWIKG7tPEHCxSib0wNuMNw6i3o1giAnPt0ch2DH0fBtaEYkq4MRKMCteFuqbX0cogXIuMewNywMvrHv4_MixhMy3L8_xIwFvTZ67jhUn4Fd5X58-jc-RPNudcP95XIjzHm9OzWfgegV1IAKjsv98XEYX1pUxm-nrOMgYWxEJSyxEpp0L_9RzKTr_LZ-ep-x5QRvVewgiozJV3mse0pHgTjbw"
By default many of the more internal claims in a token are removed from the User ClaimsPrinicpal claims.
If you want to get a specific claim into your user, you can use in the client:
}).AddOpenIDConnect(options =>
{
//Will result in that the aud claim is not removed.
options.ClaimActions.Remove("idp");
...
secondly, some of the claims are renamed and if you want to disable that renaming, you can add:
// By default, Microsoft has some legacy claim mapping that converts
// standard JWT claims into proprietary ones. This removes those mappings.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
For the API you should not need to do anything special to get the idp claim. I just ran a test with this setup in .NET 5:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMyJwtBearer(opt =>
{
opt.IncludeErrorDetails = true;
opt.MapInboundClaims = false;
opt.TokenValidationParameters.RoleClaimType = "role";
opt.TokenValidationParameters.NameClaimType = "name";
opt.Audience = "paymentapi";
opt.Authority = "https://localhost:6001";
});
services.AddControllers();
}
I did give it a test on .NET 5 and if I have this access token:
{
"nbf": 1640033816,
"exp": 1640037416,
"iss": "https://localhost:6001",
"aud": "paymentapi",
"client_id": "clientcredentialclient",
"managment": "yes",
"email": "tore#tn-data.se",
"name": "tore nestenius",
"idp": "Google",
"role": [
"admin",
"developer",
"support"
],
"website": "https://www.tn-data.se",
"jti": "5DC46A29372031F0AA6F7B62B5FDCCD6",
"iat": 1640033816,
"scope": [
"payment"
]
}
Then my user in my API controller contains the idp claim:

Response cards not showing -Amazon lex

I have created a lex chatbot and integrated it into website .Its working fine.I got to know that I have to invoke response cards from lambda .I did that too.Also,I enabled repsonse card checkbox in lex console.After all this,I am not able too see the buttons in the website.Is there any other way to display the buttons other than response cards?
Below is the code:Here,after the fulfillment,I have given the response card which means that after the message "Hey your ticket has been raised",the buttons will be displayed.This displays in lex test chatbot.
import json
import logging
import re
import http.client
import mimetypes
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def close():
val= {
"dialogAction":
{
"fulfillmentState":"Fulfilled",
"type":"Close",
"message":
{
"contentType":"PlainText",
"content":"Hey your ticket has been raised"
},
'responseCard': {
'version': '0',
'contentType': 'application/vnd.amazonaws.card.generic',
'genericAttachments': [
{
'title': 'title1',
'subTitle': 'subtitle',
"buttons":[
{
"text":"button 1",
"value":"value 1"
},
{
"text":"button 2",
"value":"value 2"
},
{
"text":"button 3",
"value":"value 3"
}
]
}
]
}
}
}
print(val)
return val
def lambda_handler(event, context):
slots = event['currentIntent']['slots']
empidemployee= event['currentIntent']["slots"]["empidemployee"]
latestdesc= event['currentIntent']["slots"]["latestdesc"]
latestservice= event['currentIntent']["slots"]["latestservice"]
latestimpactvalue= event['currentIntent']["slots"]["latestimpactvalue"]
latesturgency= event['currentIntent']["slots"]["latesturgency"]
basicinfo=event['currentIntent']["slots"]["basicinfo"]
val=close()
return val
I believe your problem is the version number. Try setting it to 1 or '1'.
I couldn't find a clear explanation as to why, but here are some evidences at least:
From the AWS example doc: Generating Response Cards Dynamically
responseCard: {
"version": 1,
...
}
I also looked deeper into the lex-web-ui files you are using and in the lex-web-ui.js file it has this:
shouldDisplayResponseCard: function shouldDisplayResponseCard() {
return this.message.responseCard && (this.message.responseCard.version === '1' || this.message.responseCard.version === 1) && this.message.responseCard.contentType === 'application/vnd.amazonaws.card.generic' && 'genericAttachments' in this.message.responseCard && this.message.responseCard.genericAttachments instanceof Array;
},
And that specifically checks for (this.message.responseCard.version === '1' || this.message.responseCard.version === 1) in order to display.

Response cards not displaying in website-lex

I have created Lex Chatbot and developed a website and integrated this Chatbot. Its working fine.But response cards in the form of buttons are not showing up.I got to know that I have to invoke it from lambda function.So I included the response card code .It works ,but after displaying the buttons it goes back and asks the first slot value again.I dont know where I am wrong
Here is the expected conversation.
User:Hi
Lex:Please provide me your eid
User:e123456
Lex:Choose one of the impact below:
1.low 2.high 3.medium (in form of buttons)
User clicks on low
Lex:Thanks,your ticket has been raised(expected)
What happens:
User:Hi
Lex:Please provide me your eid
User:e123456
Lex:Choose one of the impact below:
1.low 2.high 3.medium
User clicks on low
Lex:Please provide me your eid(goes back and asks the first slot value)
Here is my code:
import json
import logging
import re
import http.client
import mimetypes
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def elicit_slot_response(output_session_attributes,intent_name,slot_to_elicit,message):
responses= {
'dialogAction': {
'type': 'ElicitSlot',
'slottoElicit':'slot_to_elicit',
'message': {
'contentType': 'PlainText',
'content': message
},
'responseCard': {
'version': '0',
'contentType': 'application/vnd.amazonaws.card.generic',
'genericAttachments': [
{
'title': 'title1',
'subTitle': 'subtitle',
"buttons":[
{
"text":"button 1",
"value":"value 1"
},
{
"text":"button 2",
"value":"value 2"
},
{
"text":"button 3",
"value":"value 3"
}
]
}
]
}
}
}
return responses
def close():
val= {
"dialogAction":
{
"fulfillmentState":"Fulfilled",
"type":"Close",
"message":
{
"contentType":"PlainText",
"content":"Hey your ticket has been raised"
}
}
}
print(val)
return val
def lambda_handler(event, context):
val = ""
slots = event['currentIntent']['slots']
empidemployee= event['currentIntent']["slots"]["empidemployee"]
latestdesc= event['currentIntent']["slots"]["latestdesc"]
latestimpact= event['currentIntent']["slots"]["latestimpact"]
output_session_attributes = event['sessionAttributes'] if event['sessionAttributes'] is not None else {}
elicit_slot_response(output_session_attributes,'latestdetails','latestimpact',"impact")
val=close()
return val
The conversation flow restarts because in the ElicitSlot response from the Lambda function containing the response cards, you are not returning the slots parameter which would contain the slot values already taken as an input from the user.
So, include the slots parameter in the response the value for which could be event['currentIntent']['slots'].

boto3 ec2 create instance with a name

I am new to AWS and using boto3 to launch an instance. However, I notice that when I create the instance, the "Name" field is empty. So, the way I create it is as follows:
def create_instance(ami, instance_type, device_name, iam_role, volume_type,
volume_size,
security_groups, key_name, user_data):
s = boto3.Session(region_name="eu-central-1")
ec2 = s.resource('ec2')
res = ec2.create_instances(
IamInstanceProfile={'Name': iam_role},
ImageId=ami,
InstanceType=instance_type,
SecurityGroupIds=security_groups,
KeyName=key_name,
UserData=user_data,
MaxCount=1,
MinCount=1,
InstanceInitiatedShutdownBehavior='terminate',
BlockDeviceMappings=[{
'DeviceName': device_name,
'Ebs': {
'DeleteOnTermination': True,
'VolumeSize': volume_size,
'VolumeType': volume_type
}
}]
)
instance = res[0]
while instance.state['Name'] == 'pending':
time.sleep(5)
instance.load()
return instance.public_ip_address, instance.public_dns_name
There does not seem to be a simple way to specify the name of the launched instance. How can one do this?
Put a tag with key Name with your instance name as a value.
TagSpecifications=[
{
'ResourceType': 'instance',
'Tags': [
{
'Key': 'Name',
'Value': '<What you want>'
},
]
},
],

Google Classroom API patch

When executing the courses.courseWork.studentSubmissions.patch method in the Google Classroom API, a 403 error is returned when I try to update the student's submission. Below is my code.
from googleapiclient.discovery import build
from oauth2client import client
import simplejson as json
class Google:
SCOPE = {
"profile": {"scope": "profile email", "access_type": "offline"},
"classroom": {"scope": 'https://www.googleapis.com/auth/classroom.courses.readonly '
'https://www.googleapis.com/auth/classroom.rosters.readonly '
'https://www.googleapis.com/auth/classroom.profile.emails '
'https://www.googleapis.com/auth/classroom.profile.photos ',
"access_type": "offline"},
"classwork":{
"scope": "https://www.googleapis.com/auth/classroom.coursework.students https://www.googleapis.com/auth/classroom.coursework.me",
"access_type":"offline"
},
"submission":{
"scope": "https://www.googleapis.com/auth/classroom.coursework.me https://www.googleapis.com/auth/classroom.coursework.students profile email",
"access_type":"offline"
}
}
ERRORS = {
"invalid_request":{"code":"invalid_request","msg":"Invalid request. Please Try with login again."},
"account_used":{"code":"account_used","msg":"Google account is already configured with different PracTutor Account."},
"assignment_permission_denied":{"code":"assignment_permission_denied","msg":"permission denied"},
"unknown_error":{"code":"unknown_error","msg":"something went wrong."}
}
def __init__(self, code = "", genFor = "profile"):
if code:
genFor = genFor if genFor else "profile"
self.credentials = client.credentials_from_clientsecrets_and_code(pConfig['googleOauthSecretFile'],self.SCOPE[genFor]["scope"], code)
self.http_auth = self.credentials.authorize(httplib2.Http())
cred_json = self.credentials.to_json()
idinfo = json.loads(cred_json)["id_token"]
else:
raise ValueError(Google.ERRORS["invalid_request"])
def getUserInfo(self):
service = build(serviceName='oauth2', version='v2', http=self.http_auth)
idinfo = service.userinfo().get().execute()
return idinfo
def getClasses(self):
courses = []
page_token = None
service = build('classroom', 'v1', http=self.http_auth)
while True:
response = service.courses().list(teacherId="me",pageToken=page_token,
pageSize=100).execute()
courses.extend(response.get('courses', []))
page_token = response.get('nextPageToken', None)
if not page_token:
break
return courses
def getStudent(self,course_id):
students = []
page_token = None
service = build('classroom', 'v1', http=self.http_auth)
while True:
response = service.courses().students().list(courseId=course_id, pageToken=page_token,
pageSize=100).execute()
students.extend(response.get('students', []))
page_token = response.get('nextPageToken', None)
if not page_token:
break
return students
def createAssignment(self,course_id,**kwargs):
service = build('classroom', 'v1', http=self.http_auth)
date, time = kwargs["dueDate"].split(" ")
yy,mm,dd = date.split("-")
h,m,s = time.split(":")
courseWork = {
'title': kwargs["title"],
'description': kwargs["desc"],
'materials': [
{'link': { 'url': kwargs["link"] } },
],
'dueDate': {
"month": mm,
"year": yy,
"day": dd
},
'dueTime':{
"hours": h,
"minutes": m,
"seconds": s
},
'workType': 'ASSIGNMENT',
'state': 'PUBLISHED',
}
courseWork = service.courses().courseWork().create(courseId=course_id, body=courseWork).execute()
return courseWork
def submitAssignment(self,**kwargs):
service = build('classroom', 'v1', http=self.http_auth)
course_id = kwargs["courseId"]
courseWorkId = kwargs["courseWorkId"]
score = kwargs["score"]
studentSubmission = {
'assignedGrade': score,
'draftGrade': score,
'assignmentSubmission': {
'attachments': [
{
'link': {
"url": "demo.com",
"title": "Assignment1",
"thumbnailUrl": "demo.com",
}
}
],
},
'state': 'TURNED_IN',
}
gCredentials = json.loads(self.credentials.to_json())
userGId = gCredentials["id_token"]["sub"]
studentSubmissionsData = service.courses().courseWork().studentSubmissions().list(
courseId=course_id,
courseWorkId=courseWorkId,
userId=userGId).execute()
studentSubmissionId = studentSubmissionsData["studentSubmissions"][0]["id"]
courseWorkRes = service.courses().courseWork().studentSubmissions().patch(
courseId=course_id,
courseWorkId=courseWorkId,
id=studentSubmissionId,
updateMask='assignedGrade,draftGrade',
body=studentSubmission).execute()
return courseWorkRes
Method Calling
g = Google()
kwargs = {"courseId":courseId,"courseWorkId":courseWorkId,"score":80}
courseworkResponse = g.submitAssignment(**kwargs)
Error:
https://classroom.googleapis.com/v1/courses/{courses_id}/courseWork/{courseWork_id}/studentSubmissions/{studentSubmissions_id}?alt=json&updateMask=assignedGrade%2CdraftGrade
returned "The caller does not have permission">
Student's submission contains following fields assignedGrade, draftGrade, attachments (Link resource) and state.
The call is being made from an authenticated student account. The Developer Console project has the Google Classroom API enabled, and other calls to the Google Classroom API are working fine, such as courses.courseWork.create and courses.courseWork.studentSubmissions.list. Also I am making request from the same Developer Console project from course work item is associated/created.
The same error 403 error with different message is returned when I am trying from Google API explorer.
{
"error": {
"code": 403,
"message": "#ProjectPermissionDenied The Developer Console project is not permitted to make this request.",
"status": "PERMISSION_DENIED"
}
}
Any help would be appreciated, thank you
That error message basically means that you don't have permission to do what it is you are trying to do. The permissions are related to what scopes you have authenticated the user with. here is a full list of scopes
Method: courses.courseWork.studentSubmis.patch requires the following scopes.
Authorization
Requires one of the following OAuth scopes:
https://www.googleapis.com/auth/classroom.coursework.students
https://www.googleapis.com/auth/classroom.coursework.me
Use List and Get before patch
Use list and get before patch to be sure that you have the corect ids.
If you have the user preform a list first then find the one you are after then preform a get you can make changes to the object in the get then run your update on that. Doing it this way ensures that all the ids you are passing are correct and that the user does in fact have access to what they are trying to update.

Resources