def PUT(self, request, **kwargs): if not can_manage_external_tools(): return self.json_response('{"error":"Unauthorized"}', status=401) canvas_id = kwargs['canvas_id'] try: json_data = json.loads(request.body).get('external_tool', {}) self.validate(json_data) except Exception as ex: logger.error('PUT ExternalTool error: %s' % ex) return self.json_response('{"error": "%s"}' % ex, status=400) try: external_tool = ExternalTool.objects.get(canvas_id=canvas_id) curr_data = external_tool.json_data() keystore = BLTIKeyStore.objects.get( consumer_key=curr_data['consumer_key']) except ExternalTool.DoesNotExist: return self.json_response( '{"error":"external_tool %s not found"}' % canvas_id, status=404) except BLTIKeyStore.DoesNotExist: keystore = BLTIKeyStore() # PUT does not update canvas_id or account_id external_tool.config = json.dumps(json_data['config']) external_tool.changed_by = UserService().get_original_user() external_tool.changed_date = datetime.utcnow().replace(tzinfo=utc) external_tool.save() keystore.consumer_key = json_data['config']['consumer_key'] shared_secret = json_data['config']['shared_secret'] if (shared_secret is None or not len(shared_secret)): del json_data['config']['shared_secret'] else: keystore.shared_secret = shared_secret try: new_config = ExternalTools().update_external_tool_in_account( external_tool.account.account_id, json_data['config']['id'], json_data['config']) external_tool.canvas_id = new_config.get('id') external_tool.config = json.dumps(new_config) external_tool.provisioned_date = datetime.utcnow().replace( tzinfo=utc) external_tool.save() if keystore.shared_secret: keystore.save() logger.info('%s updated External Tool "%s"' % ( external_tool.changed_by, external_tool.canvas_id)) except DataFailureException as err: return self.json_response( '{"error":"%s: %s"}' % (err.status, err.msg), status=500) return self.json_response(json.dumps({ 'external_tool': external_tool.json_data()}))
def test_get_sessionless_launch_from_course_sis_id(self): with self.settings( RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): canvas = ExternalTools() launch = canvas.get_sessionless_launch_url_from_course_sis_id('54321', '2015-autumn-UWBW-301-A') self.assertEquals(launch['id'], 54321, "Has correct tool id")
def test_get_external_tools_in_account(self): with self.settings( RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): canvas = ExternalTools() tools = canvas.get_external_tools_in_account('12345') self.assertEquals(len(tools), 12, "Correct tools length") self.assertEquals(tools[10]['name'], "Tool", "Name is Correct")
def test_get_external_tools_in_course_by_sis_id(self): with self.settings( RESTCLIENTS_CANVAS_DAO_CLASS='restclients.dao_implementation.canvas.File'): canvas = ExternalTools() tools = canvas.get_external_tools_in_course_by_sis_id('2015-autumn-UWBW-301-A') self.assertEquals(len(tools), 2, "Correct tools length") self.assertEquals(tools[1]['name'], 'Course Tool', "Has correct tool name")
class Command(BaseCommand): help = """Insert LTI tools into Canvas. see: https://canvas.instructure.com/doc/api/external_tools.html#method.external_tools.show for an example of what the JSON config should look like, which can be one or an array of app configs. """ def handle(self, *args, **options): self.external_tools = ExternalTools() self.tool_set = {} if len(args) != 1 or not os.access(args[0], os.R_OK): print >> self.stderr, "ERROR: invalid json config" return try: with open(args[0], 'r') as jsonfile: lticonf = json.loads(jsonfile.read()) if isinstance(lticonf, list): for conf in lticonf: self.load_lti(conf) else: self.load_lti(lticonf) except Exception as err: print >> self.stderr, 'ERROR: %s' % err def load_lti(self, conf): try: canvas_account = conf.get('account_canvas_id') if not canvas_account: sis_id = conf.get('account_sis_id') if sis_id: canvas_account = self.external_tools.sis_account_id(sis_id) if canvas_account: if canvas_account not in self.tool_set: self.tool_set[canvas_account] = self.external_tools.get_external_tools_in_account(canvas_account) for tool in self.tool_set[canvas_account]: if (tool.get('url') == conf.get('url') and tool.get('consumer_key') == conf.get('consumer_key')): print >> self.stderr, '"%s" already installed in %s' % (conf.get('name'), canvas_account) return tool = self.external_tools.add_external_tool_to_account(canvas_account, **conf) print 'Installed \"%s\" (%s) in %s' % (conf.get('name'), tool.get('id'), canvas_account) except DataFailureException as err: raise
def handle(self, *args, **options): self._canvas = Canvas() self._tools = ExternalTools() self._accounts = Accounts() self._courses = Courses() self._options = options csv.register_dialect("unix_newline", lineterminator="\n") self._writer = csv.writer(sys.stdout, dialect="unix_newline") self._headers = ['tool_name', 'tool_id', 'tool_type', 'account_name', 'account_id'] if self._options['courses']: self._headers.append('course_name') self._headers.append('course_id') self._headers.append('term') if options['sessionless']: self._headers.append('sessionless url') accounter = self._accounts.get_account if re.match(r'^\d+$', options['account_id']) \ else self._accounts.get_account_by_sis_id try: self.report_external_tools(accounter(options['account_id'])) except DataFailureException as err: if err.status == 404: print >> sys.stderr, 'Unknown Sub-Account \"%s\"' % (options['account_id'])
def handle(self, *args, **options): self._accounts = Accounts() self._tools = ExternalTools() account = self._accounts.get_account( getattr(settings, 'RESTCLIENTS_CANVAS_ACCOUNT_ID')) self.update_tools_in_account(account) self.update_job()
def handle(self, *args, **options): self.external_tools = ExternalTools() self.tool_set = {} if len(args) != 1 or not os.access(args[0], os.R_OK): print >> self.stderr, "ERROR: invalid json config" return try: with open(args[0], 'r') as jsonfile: lticonf = json.loads(jsonfile.read()) if isinstance(lticonf, list): for conf in lticonf: self.load_lti(conf) else: self.load_lti(lticonf) except Exception as err: print >> self.stderr, 'ERROR: %s' % err
class Command(SISProvisionerCommand): help = "Sync LTI Manager app with actual external tools in Canvas" def handle(self, *args, **options): self._accounts = Accounts() self._tools = ExternalTools() account = self._accounts.get_account( getattr(settings, 'RESTCLIENTS_CANVAS_ACCOUNT_ID')) self.update_tools_in_account(account) self.update_job() def update_tools_in_account(self, account): for tool in self._tools.get_external_tools_in_account( account.account_id): self.update_model(account, tool) for subaccount in self._accounts.get_sub_accounts(account.account_id): self.update_tools_in_account(subaccount) def update_model(self, account, config): canvas_id = config['id'] try: external_tool = ExternalTool.objects.get(canvas_id=canvas_id) except ExternalTool.DoesNotExist: external_tool = ExternalTool(canvas_id=canvas_id) try: et_account = ExternalToolAccount.objects.get( account_id=account.account_id) except ExternalToolAccount.DoesNotExist: et_account = ExternalToolAccount(account_id=account.account_id) et_account.sis_account_id = account.sis_account_id et_account.name = account.name et_account.save() external_tool.account = et_account external_tool.config = json.dumps(config) external_tool.changed_by = 'auto' external_tool.changed_date = datetime.utcnow().replace(tzinfo=utc) external_tool.save()
class Command(BaseCommand): help = "Report externals tools in account" option_list = BaseCommand.option_list + ( make_option('-a', '--account', action='store', dest='account_id', type="string", default=default_account, help='show external tools in account by id or sis_id (default: %s)' % default_account), make_option('-r', '--recurse', action='store_true', dest='recurse', default=False, help='recurse through subaccounts'), make_option('-c', '--courses', action='store_true', dest='courses', default=False, help='include account courses in report'), make_option('-t', '--term', action='store', dest='term', type="string", default='', help='include only courses in given term'), make_option('-s', '--sessionless-url', action='store_true', dest='sessionless', default=False, help='show sessionless url with each external tool'), ) def handle(self, *args, **options): self._canvas = Canvas() self._tools = ExternalTools() self._accounts = Accounts() self._courses = Courses() self._options = options csv.register_dialect("unix_newline", lineterminator="\n") self._writer = csv.writer(sys.stdout, dialect="unix_newline") self._headers = ['tool_name', 'tool_id', 'tool_type', 'account_name', 'account_id'] if self._options['courses']: self._headers.append('course_name') self._headers.append('course_id') self._headers.append('term') if options['sessionless']: self._headers.append('sessionless url') accounter = self._accounts.get_account if re.match(r'^\d+$', options['account_id']) \ else self._accounts.get_account_by_sis_id try: self.report_external_tools(accounter(options['account_id'])) except DataFailureException as err: if err.status == 404: print >> sys.stderr, 'Unknown Sub-Account \"%s\"' % (options['account_id']) def report_external_tools(self, account): tools = self._tools.get_external_tools_in_account(account.account_id) self._print_tools(tools, account) if self._options['courses']: params = { "by_subaccounts":[account.account_id], "include": ["term"] } if self._options['term']: params['enrollment_term_id'] = self._canvas.get_term_by_sis_id(self._options['term']).term_id courses = self._courses.get_published_courses_in_account(account.account_id, params=params) for course in courses: tools = self._tools.get_external_tools_in_course(course.course_id) self._print_tools(tools, account, course) if self._options['recurse']: subaccounts = self._accounts.get_sub_accounts(account.account_id) for account in subaccounts: self.report_external_tools(account) def _print_tools(self, tools, account, course=None): if len(tools): if self._headers: self._writer.writerow(self._headers) self._headers = None for tool in tools: tool_types = [] for tt in ['account', 'course', 'user']: if tool.get("%s_navigation" % tt): tool_types.append(tt) tool_type = ' & '.join(tool_types) line = [tool['name'], tool['id'], tool_type, account.name, account.account_id] if self._options['courses']: if course: line.extend([course.name, course.course_id, course.term.name if course.term else '']) else: line.extend(['','','']) if self._options['sessionless']: try: sessionless = self._tools.get_sessionless_launch_url_from_account(tool['id'], account.account_id) line.append(sessionless['url']) except DataFailureException as ex: line.append('') self._writer.writerow(line)
def POST(self, request, **kwargs): if not can_manage_external_tools(): return self.json_response('{"error":"Unauthorized"}', status=401) try: json_data = json.loads(request.body).get('external_tool', {}) self.validate(json_data) except Exception as ex: logger.error('POST ExternalTool error: %s' % ex) return self.json_response('{"error": "%s"}' % ex, status=400) account_id = json_data['account_id'] canvas_id = json_data['config'].get('id') try: external_tool = ExternalTool.objects.get(canvas_id=canvas_id) return self.json_response( '{"error": "External tool %s already exists"}' % ex, status=400) except ExternalTool.DoesNotExist: pass try: account = ExternalToolAccount.objects.get(account_id=account_id) except ExternalToolAccount.DoesNotExist: account = ExternalToolAccount(account_id=account_id) try: canvas_account = Accounts().get_account(account_id) account.name = canvas_account.name account.sis_account_id = canvas_account.sis_account_id except DataFailureException as ex: pass account.save() external_tool = ExternalTool(canvas_id=canvas_id) external_tool.account = account external_tool.config = json.dumps(json_data['config']) external_tool.changed_by = UserService().get_original_user() external_tool.changed_date = datetime.utcnow().replace(tzinfo=utc) try: keystore = BLTIKeyStore.objects.get( consumer_key=json_data['config']['consumer_key']) # Re-using an existing key/secret (clone?) json_data['config']['shared_secret'] = keystore.shared_secret except BLTIKeyStore.DoesNotExist: keystore = BLTIKeyStore() keystore.consumer_key = json_data['config']['consumer_key'] shared_secret = json_data['config']['shared_secret'] if (shared_secret is None or not len(shared_secret)): if not canvas_id: # New external tool, generate a secret shared_secret = external_tool.generate_shared_secret() keystore.shared_secret = shared_secret json_data['config']['shared_secret'] = shared_secret else: # Existing external tool, don't overwrite the secret del json_data['config']['shared_secret'] keystore.save() try: if not canvas_id: new_config = ExternalTools().create_external_tool_in_account( account_id, json_data['config']) logger.info('%s created External Tool "%s"' % ( external_tool.changed_by, new_config.get('id'))) else: new_config = ExternalTools().update_external_tool_in_account( account_id, canvas_id, json_data['config']) logger.info('%s updated External Tool "%s"' % ( external_tool.changed_by, new_config.get('id'))) external_tool.canvas_id = new_config.get('id') external_tool.config = json.dumps(new_config) external_tool.provisioned_date = datetime.utcnow().replace( tzinfo=utc) external_tool.save() except DataFailureException as err: return self.json_response( '{"error":"%s: %s"}' % (err.status, err.msg), status=500) return self.json_response(json.dumps({ 'external_tool': external_tool.json_data()}))