We use a Google form which is meant to create recurring Classroom assignments through Google Apps Script. The script runs successfully once a user opens the form in edit mode, as she/he is prompted to authorise script execution scopes.
What is not working is when a generic user is accessing the form to only submit data: in this case there's no prompt to authorise script scopes execution, therefore the onSubmit
script will run, throwing the following error:
GoogleJsonResponseException: API call to classroom.courses.courseWork.create failed with error: The caller does not have permission at onSubmit
Offending code:
var ret = Classroom.Courses.CourseWork.create(ClassSource, course);
BTW, we have set all scopes in manifest:
{
"timeZone": "Europe/Rome",
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Classroom",
"version": "v1",
"serviceId": "classroom"
}
]
},
"oauthScopes": [
"https://www.googleapis.com/auth/classroom.courses",
"https://www.googleapis.com/auth/classroom.coursework.me.readonly",
"https://www.googleapis.com/auth/classroom.profile.emails",
"https://www.googleapis.com/auth/classroom.profile.photos",
"https://www.googleapis.com/auth/classroom.rosters",
"https://www.googleapis.com/auth/classroom.coursework.me",
"https://www.googleapis.com/auth/classroom.coursework.me.readonly",
"https://www.googleapis.com/auth/classroom.coursework.students",
"https://www.googleapis.com/auth/classroom.coursework.students.readonly"
],
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
I guess this is due to the fact the user is never prompted to authorise..... Is there a way to trigger the authorisation prompt when the form is being opened somehow? Notice that the script uses multiple functions, and this is not helping triggering permissions authorisation as suggested here
Full script code:
function onSubmit(){
var subjects = [
['zzzzzzzzzzzz','Y9 - Art 2022-23'],
['yyyyyyyyyyyy','Y7 - Computing 2022-23'],
['xxxxxxxxxxxx','Y8 - English 2022-23']
];
try{
var formResponses = FormApp.getActiveForm().getResponses();
var length = formResponses.length;
var lastResponse = formResponses[length-1];
var respondent = lastResponse.getRespondentEmail();
var course = null;
var items = lastResponse.getItemResponses();
for (i in items){
var item = items[i].getItem();
var response = items[i].getResponse();
if (response){
var title = item.getTitle();
switch (title){
case 'Select the course':
var hwSubject = response;
var index = indexOf2dArray(subjects,response);
if (index[0] >=0){
// we've got the subject, grab course id
course = subjects[index[0]][0];
}
break;
case 'Homework title':
var hwTitle = response;
break;
case 'Instructions':
var hwInstructions = response;
break;
case 'Begin date':
var beginTextDate = response;
break;
case 'End date':
var endTextDate = response;
break;
case 'Status':
var status = (response == "Draft" ? 'DRAFT' : 'PUBLISHED');
break;
case 'Assignment Time slot':
var timeSlot = response;
break;
}
}
}
var beginDate = new Date(beginTextDate);
var endDate = new Date(endTextDate);
var creationResponse = '';
if (course && beginDate < endDate){
while(beginDate <= endDate){
console.log(beginDate);
var dueDate = {
"year": beginDate.getUTCFullYear(),
"month": beginDate.getUTCMonth()+1,
"day": beginDate.getUTCDate()
}
var dueTime = {
"hours": parseInt(timeSlot.split(':')[0]),
"minutes": parseInt(timeSlot.split(':')[1]),
"seconds": 0,
"nanos": 0
}
var ClassSource = {
title: hwTitle,
description: hwInstructions,
state: status,
dueDate : dueDate,
dueTime : dueTime,
workType: "ASSIGNMENT"
};
var ret = Classroom.Courses.CourseWork.create(ClassSource, course);
creationResponse += `<br> Assignment status <a href="${ret.alternateLink}">${status}</a>, due date:${JSON.stringify(dueDate)}`;
Utilities.sleep(500);
var newDate = beginDate.setDate(beginDate.getDate() + 1);
beginDate = new Date(newDate);
}// end while
var emailSubject = "** Classroom recurring homework form response";
var emailBody = `<strong>Recurring assignment creation response</strong><br>Course: ${hwSubject}, begin date: ${beginTextDate}, end date: ${endTextDate} <br/>`;
emailBody += '<br><strong>Homework title:</strong> ' + hwTitle;
emailBody += '<br><strong>Homework instructions:</strong> ' + hwInstructions + '<br><br>';
emailBody += `<br/><strong>Status:</strong> ${creationResponse}<br/>`;
var emailRecipients = respondent;
doEmail(emailSubject,emailBody,emailRecipients);
}else{
var emailSubject = "** Classroom recurring homework form error";
var emailBody = `<strong>Course not selected or invalide date ranges</strong><br>course: ${course}, begin date: ${beginTextDate}, end date: ${endTextDate} <br/>`;
var emailRecipients = respondent;
doEmail(emailSubject,emailBody,emailRecipients);
}
}catch(err){
console.log(err);
var emailSubject = "** Classroom recurring homework form error";
var emailBody = "<strong>This is an automated message.</strong><br/>";
emailBody += `<br/>Could not schedule homework assignment.<br/>`;
emailBody += `<br/>${err.stack}<br/>`;
var emailRecipients = respondent;
doEmail(emailSubject,emailBody,emailRecipients);
}
}
function indexOf2dArray(array2d, itemtofind) {
index = [].concat.apply([], ([].concat.apply([], array2d))).indexOf(itemtofind);
// return "false" if the item is not found
if (index === -1) { return false; }
// Use any row to get the rows' array length
// Note, this assumes the rows are arrays of the same length
numColumns = array2d[0].length;
// row = the index in the 1d array divided by the row length (number of columns)
row = parseInt(index / numColumns);
// col = index modulus the number of columns
col = index % numColumns;
return [row, col];
}
function doEmail(emailSubject, emailBody,emailRecipients){
MailApp.sendEmail({
from: Session.getActiveUser().getEmail,
to: emailRecipients,
subject: emailSubject,
htmlBody: emailBody,
name: "Our Mailing"
});
}
EDIT:
To allow proper operations, not only the user is required to be a Form editor, but the user is required to debug at least once the onSubmit
script, which will trigger the authorisation process.
I guess this is a Google Forms architecture limitation, since the onSubmit function is run on the server side, defeating any possibility to trigger authorisation on the client side beforehand :-(