Example #1
0
    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)
Example #11
0
    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()}))