def build_approval_notification_message(self, nt, approval_status): env = sandbox.ImmutableSandboxedEnvironment() context = self.context(approval_status) msg_template = body_template = None msg = body = '' # Use custom template if available if nt.messages and nt.messages.get('workflow_approval', None): template = nt.messages['workflow_approval'].get( approval_status, {}) msg_template = template.get('message', None) body_template = template.get('body', None) # If custom template not provided, look up default template default_template = nt.notification_class.default_messages[ 'workflow_approval'][approval_status] if not msg_template: msg_template = default_template.get('message', None) if not body_template: body_template = default_template.get('body', None) if msg_template: try: msg = env.from_string(msg_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): msg = '' if body_template: try: body = env.from_string(body_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): body = '' return (msg, body)
def build_notification_message(self, nt, status): env = sandbox.ImmutableSandboxedEnvironment() from awx.api.serializers import UnifiedJobSerializer job_serialization = UnifiedJobSerializer(self).to_representation(self) context = self.context(job_serialization) msg_template = body_template = None msg = body = '' # Use custom template if available if nt.messages: template = nt.messages.get(self.STATUS_TO_TEMPLATE_TYPE[status], {}) or {} msg_template = template.get('message', None) body_template = template.get('body', None) # If custom template not provided, look up default template default_template = nt.notification_class.default_messages[self.STATUS_TO_TEMPLATE_TYPE[status]] if not msg_template: msg_template = default_template.get('message', None) if not body_template: body_template = default_template.get('body', None) if msg_template: try: msg = env.from_string(msg_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): msg = '' if body_template: try: body = env.from_string(body_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): body = '' return (msg, body)
def build_notification_message(self, nt, status): env = sandbox.ImmutableSandboxedEnvironment() from awx.api.serializers import UnifiedJobSerializer job_serialization = UnifiedJobSerializer(self).to_representation(self) context = self.context(job_serialization) msg_template = body_template = None if nt.messages: templates = nt.messages.get(self.STATUS_TO_TEMPLATE_TYPE[status], {}) or {} msg_template = templates.get('message', {}) body_template = templates.get('body', {}) if msg_template: try: notification_subject = env.from_string(msg_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): notification_subject = '' else: notification_subject = u"{} #{} '{}' {}: {}".format(self.get_notification_friendly_name(), self.id, self.name, status, self.get_ui_url()) notification_body = self.notification_data() notification_body['friendly_name'] = self.get_notification_friendly_name() if body_template: try: notification_body['body'] = env.from_string(body_template).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): notification_body['body'] = '' return (notification_subject, notification_body)
def get_jinja_environment(template, extra_globals=None): """Return a sandboxed jinja environment.""" template_map = {'template': template} env = sandbox.ImmutableSandboxedEnvironment( loader=jinja2.DictLoader(template_map), bytecode_cache=CompilerCache()) env.filters['prepend'] = do_prepend env.filters['preserve'] = preserve_linefeeds env.globals['json'] = json if extra_globals: env.globals.update(extra_globals) return env
def build_notification_message(self, event_type, context): env = sandbox.ImmutableSandboxedEnvironment() templates = self.get_message(event_type) msg_template = templates.get('message', {}) try: notification_subject = env.from_string(msg_template).render( **context) except (TemplateSyntaxError, UndefinedError, SecurityError): notification_subject = '' msg_body = templates.get('body', {}) try: notification_body = env.from_string(msg_body).render(**context) except (TemplateSyntaxError, UndefinedError, SecurityError): notification_body = '' return (notification_subject, notification_body)
def inject_credential(self, credential, env, safe_env, args, private_data_dir): """ Inject credential data into the environment variables and arguments passed to `ansible-playbook` :param credential: a :class:`awx.main.models.Credential` instance :param env: a dictionary of environment variables used in the `ansible-playbook` call. This method adds additional environment variables based on custom `env` injectors defined on this CredentialType. :param safe_env: a dictionary of environment variables stored in the database for the job run (`UnifiedJob.job_env`); secret values should be stripped :param args: a list of arguments passed to `ansible-playbook` in the style of `subprocess.call(args)`. This method appends additional arguments based on custom `extra_vars` injectors defined on this CredentialType. :param private_data_dir: a temporary directory to store files generated by `file` injectors (like config files or key files) """ if not self.injectors: if self.managed_by_tower and credential.credential_type.namespace in dir(builtin_injectors): injected_env = {} getattr(builtin_injectors, credential.credential_type.namespace)(credential, injected_env, private_data_dir) env.update(injected_env) safe_env.update(build_safe_env(injected_env)) return class TowerNamespace: pass tower_namespace = TowerNamespace() # maintain a normal namespace for building the ansible-playbook arguments (env and args) namespace = {'tower': tower_namespace} # maintain a sanitized namespace for building the DB-stored arguments (safe_env) safe_namespace = {'tower': tower_namespace} # build a normal namespace with secret values decrypted (for # ansible-playbook) and a safe namespace with secret values hidden (for # DB storage) injectable_fields = list(credential.inputs.keys()) + credential.dynamic_input_fields for field_name in list(set(injectable_fields)): value = credential.get_input(field_name) if type(value) is bool: # boolean values can't be secret/encrypted/external safe_namespace[field_name] = namespace[field_name] = value continue if field_name in self.secret_fields: safe_namespace[field_name] = '**********' elif len(value): safe_namespace[field_name] = value if len(value): namespace[field_name] = value for field in self.inputs.get('fields', []): # default missing boolean fields to False if field['type'] == 'boolean' and field['id'] not in credential.inputs.keys(): namespace[field['id']] = safe_namespace[field['id']] = False # make sure private keys end with a \n if field.get('format') == 'ssh_private_key': if field['id'] in namespace and not namespace[field['id']].endswith('\n'): namespace[field['id']] += '\n' file_tmpls = self.injectors.get('file', {}) # If any file templates are provided, render the files and update the # special `tower` template namespace so the filename can be # referenced in other injectors sandbox_env = sandbox.ImmutableSandboxedEnvironment() for file_label, file_tmpl in file_tmpls.items(): data = sandbox_env.from_string(file_tmpl).render(**namespace) _, path = tempfile.mkstemp(dir=private_data_dir) with open(path, 'w') as f: f.write(data) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) # FIXME: develop some better means of referencing paths inside containers container_path = os.path.join('/runner', os.path.basename(path)) # determine if filename indicates single file or many if file_label.find('.') == -1: tower_namespace.filename = container_path else: if not hasattr(tower_namespace, 'filename'): tower_namespace.filename = TowerNamespace() file_label = file_label.split('.')[1] setattr(tower_namespace.filename, file_label, container_path) injector_field = self._meta.get_field('injectors') for env_var, tmpl in self.injectors.get('env', {}).items(): try: injector_field.validate_env_var_allowed(env_var) except ValidationError as e: logger.error('Ignoring prohibited env var {}, reason: {}'.format(env_var, e)) continue env[env_var] = sandbox_env.from_string(tmpl).render(**namespace) safe_env[env_var] = sandbox_env.from_string(tmpl).render(**safe_namespace) if 'INVENTORY_UPDATE_ID' not in env: # awx-manage inventory_update does not support extra_vars via -e extra_vars = {} for var_name, tmpl in self.injectors.get('extra_vars', {}).items(): extra_vars[var_name] = sandbox_env.from_string(tmpl).render(**namespace) def build_extra_vars_file(vars, private_dir): handle, path = tempfile.mkstemp(dir=private_dir) f = os.fdopen(handle, 'w') f.write(safe_dump(vars)) f.close() os.chmod(path, stat.S_IRUSR) return path if extra_vars: path = build_extra_vars_file(extra_vars, private_data_dir) # FIXME: develop some better means of referencing paths inside containers container_path = os.path.join('/runner', os.path.basename(path)) args.extend(['-e', '@%s' % container_path])
def validate(self, value, model_instance): super(CredentialTypeInjectorField, self).validate(value, model_instance) # make sure the inputs are valid first try: CredentialTypeInputField().validate(model_instance.inputs, model_instance) except django_exceptions.ValidationError: # If `model_instance.inputs` itself is invalid, we can't make an # estimation as to whether our Jinja templates contain valid field # names; don't continue return # In addition to basic schema validation, search the injector fields # for template variables and make sure they match the fields defined in # the inputs valid_namespace = dict( (field, 'EXAMPLE') for field in model_instance.defined_fields) class ExplodingNamespace: def __str__(self): raise UndefinedError( _('Must define unnamed file injector in order to reference `tower.filename`.' )) class TowerNamespace: def __init__(self): self.filename = ExplodingNamespace() def __str__(self): raise UndefinedError( _('Cannot directly reference reserved `tower` namespace container.' )) valid_namespace['tower'] = TowerNamespace() # ensure either single file or multi-file syntax is used (but not both) template_names = [ x for x in value.get('file', {}).keys() if x.startswith('template') ] if 'template' in template_names: valid_namespace['tower'].filename = 'EXAMPLE_FILENAME' if len(template_names) > 1: raise django_exceptions.ValidationError( _('Must use multi-file syntax when injecting multiple files' ), code='invalid', params={'value': value}, ) elif template_names: for template_name in template_names: template_name = template_name.split('.')[1] setattr(valid_namespace['tower'].filename, template_name, 'EXAMPLE_FILENAME') for type_, injector in value.items(): if type_ == 'env': for key in injector.keys(): self.validate_env_var_allowed(key) for key, tmpl in injector.items(): try: sandbox.ImmutableSandboxedEnvironment( undefined=StrictUndefined).from_string(tmpl).render( valid_namespace) except UndefinedError as e: raise django_exceptions.ValidationError( _('{sub_key} uses an undefined field ({error_msg})'). format(sub_key=key, error_msg=e), code='invalid', params={'value': value}, ) except SecurityError as e: raise django_exceptions.ValidationError( _('Encountered unsafe code execution: {}').format(e)) except TemplateSyntaxError as e: raise django_exceptions.ValidationError( _('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})' ).format(sub_key=key, type=type_, error_msg=e), code='invalid', params={'value': value}, )
process_access_request, ) from grandchallenge.evaluation.utils import get from grandchallenge.hanging_protocols.models import ViewContentMixin from grandchallenge.modalities.models import ImagingModality from grandchallenge.organizations.models import Organization from grandchallenge.publications.models import Publication from grandchallenge.subdomains.utils import reverse from grandchallenge.workstations.models import Workstation logger = logging.getLogger(__name__) DEFAULT_INPUT_INTERFACE_SLUG = "generic-medical-image" DEFAULT_OUTPUT_INTERFACE_SLUG = "generic-overlay" JINJA_ENGINE = sandbox.ImmutableSandboxedEnvironment() class Algorithm(UUIDModel, TitleSlugDescriptionModel, ViewContentMixin): editors_group = models.OneToOneField( Group, on_delete=models.PROTECT, editable=False, related_name="editors_of_algorithm", ) users_group = models.OneToOneField( Group, on_delete=models.PROTECT, editable=False, related_name="users_of_algorithm", )