async def process(self, req: Request['FastExecute_GET.Arguments'], user: User) -> None: configId = req.args.configId tagkey = req.args.tagkey tagvalue = req.args.tagvalue # pylint: disable=attribute-defined-outside-init # Tag key and value must be provided both or neither. if tagkey is None and tagvalue is not None: raise PresentableError( xhtml.p['Got "tagkey" without "tagvalue".']) if tagkey is not None and tagvalue is None: raise PresentableError( xhtml.p['Got "tagvalue" without "tagkey".']) # Either configId or tag key+value must be provided. if configId is None: if tagkey is None: raise PresentableError( xhtml.p['Either "configId" or "tagkey" + "tagvalue" ' 'is required.']) assert tagvalue is not None # Look up tag key+value. self.configs = sorted( self.configDB.iterConfigsByTag(tagkey, tagvalue)) else: if tagkey is not None: raise PresentableError(xhtml.p[ 'Providing both "configId" and "tagkey" + "tagvalue" ' 'is not allowed.']) # Look up configId. try: self.configs = [self.configDB[configId]] except KeyError: self.configs = []
def verifyToken(tokenDB: TokenDB, args: PasswordSetArgs) -> Token: """Verify a password reset token. @return: A valid token matching the given arguments. @raise PresentableError: If there is no valid token corresponding to the given arguments. """ tokenId = args.token credentials = Credentials(tokenId, args.secret) try: token = authenticateToken(tokenDB, credentials) except KeyError as ex: raise PresentableError(xhtml[f'Token {tokenId} does not exist. ' f'Perhaps it was already used?']) from ex except UnauthorizedLogin as ex: raise PresentableError(xhtml[ f'The provided secret was not correct for token {tokenId}: {ex}'] ) from ex if token.role is not TokenRole.PASSWORD_RESET: raise PresentableError( xhtml[f'Token {tokenId} is not a password reset token.']) if token.expired: raise PresentableError( xhtml[f'Token {tokenId} has expired. ' f'Please ask the factory operator to reset the password.']) return token
async def process(self, req: Request['UserList_POST.Arguments'], user: User) -> None: # Find user record. userName = req.args.user try: subject = self.userDB[userName] except KeyError: raise PresentableError( xhtml.p(class_='notice') [f'There is no user named "{userName}"']) # Parse and check all changes. requestUserName = user.name newRoles = uiRoleToSet(req.args.role) if (userName == requestUserName and not rolesGrantPrivilege(newRoles, 'u/m')): # Prevent user from revoking their own 'u/m' privilege. raise PresentableError(xhtml[xhtml.p( class_='notice' )['Revoking your own privileges could lead to ' 'a situation from which recovery is impossible.'], xhtml.p[ f'If you want to change the role of user "{userName}", ' f'please log in as another user with operator ' f'privileges.'], ]) # Changes are OK, commit them. subject.roles = newRoles raise Redirect( pageURL('UserList', UserList_GET.Arguments.subset(req.args)))
def _checkState(self) -> None: """Check against making parameters final which are overridden in existing task defs. """ args = self.args # TODO: Generalize this when we support a variable number of levels. for index, name in cast(DictArgInstance[str], args.params).items(): if name == '': continue final = index in cast(DictArgInstance[bool], args.final) if final: for taskDef in self.taskDefDB: if taskDef.frameworkId == args.id and \ name in taskDef.getParametersSelf(): raise PresentableError(xhtml.p[ f'Cannot make parameter "{name}" final, ' f'because it is overridden ' f'by task "{taskDef.getId()}".']) if args.wrapper == '': raise PresentableError( xhtml.p['Value of the wrapper field cannot be empty. ' 'If you have no special wishes, ' 'use the framework name as the wrapper name.']) checkParamState(args, paramTop) checkResourceRequirementsState(self.resTypeDB, args)
async def process(self, req: Request['SetPassword_POST.Arguments'], user: User) -> None: # pylint: disable=attribute-defined-outside-init try: token = verifyToken(self.tokenDB, req.args) except PresentableError: self.userName = None raise else: self.userName = userName = token.getParam('name') password = req.args.password credentials = Credentials(userName, password) if password == req.args.password2: quality = passwordQuality(credentials) else: quality = PasswordMessage.MISMATCH if quality is not PasswordMessage.SUCCESS: raise PresentableError(xhtml[passwordStr[quality]]) try: setPassword(self.userDB, credentials) except ValueError as ex: raise PresentableError(xhtml[ex.args[0]]) else: self.tokenDB.remove(token)
def fetchRecordForDeletion(proc: RecordDeleteProcessor[DeleteArgsT, DBRecord], recordId: str) -> DBRecord: """Tries to fetch the record with the given ID. Raises PresentableError if the record does not exist or can currently not be deleted. """ try: record = proc.db[recordId] except KeyError: raise PresentableError( xhtml.p['Cannot delete ', proc.recordName, ' ', xhtml.b[recordId], ' because it does not exist (anymore).']) try: proc.checkState(record) except RecordInUseError as ex: raise PresentableError( xhtml.p['Cannot delete ', proc.recordName, ' ', xhtml.b[recordId], ' because it is used in the following ', pluralize(ex.refererName, ex.referers), ':'] + unorderedList[(ex.presenter(referer) for referer in sorted(ex.referers))].present()) return record
async def process(self, req: Request['ChangePassword_POST.Arguments'], user: User) -> None: # pylint: disable=attribute-defined-outside-init if req.args.action is Actions.CANCEL: page = cast(ChangePassword_POST, self.page) raise Redirect(page.getCancelURL(req.args)) elif req.args.action is Actions.CHANGE: userDB = self.userDB userName = user.name # get current logged-in user if userName is None: self.retry = False raise PresentableError( xhtml["Anonymous user has no password."]) # Perform sanity checks on new password. password = req.args.password newCredentials = Credentials(userName, password) if password == req.args.password2: quality = passwordQuality(newCredentials) else: quality = PasswordMessage.MISMATCH if quality is not PasswordMessage.SUCCESS: self.retry = True raise PresentableError(xhtml[passwordStr[quality]]) oldCredentials = Credentials(userName, req.args.loginpass) try: user_ = await authenticateUser(userDB, oldCredentials) except LoginFailed as ex: self.retry = True raise PresentableError( xhtml["Verification of old password failed", f": {ex.args[0]}" if ex.args else None, "."]) # Apply changes. try: setPassword(userDB, newCredentials) except ValueError as ex: self.retry = True raise PresentableError(xhtml[ex.args[0]]) else: # Successfully changed password raise Redirect( pageURL( 'ChangePassword', ChangePassword_GET.Arguments( user=userName, msg=PasswordMessage.SUCCESS))) else: assert False, req.args.action
def _checkState(self) -> None: if not self.args.restype: raise PresentableError(xhtml.p[ 'No resource type was selected.' ]) resTypeName = self.args.restype if resTypeName not in self.resTypeDB: raise PresentableError(xhtml.p[ 'Resource type ', xhtml.b[resTypeName], ' does not exist (anymore).' ]) if resTypeName == taskRunnerResourceTypeName: raise PresentableError(xhtml.p[ 'This page cannot be used to create Task Runners.' ])
async def process(self, req: Request['ChangePassword_GET.Arguments'], user: User) -> None: # pylint: disable=attribute-defined-outside-init # This page will not be linked for anonymous users, but they can # of course still navigate here manually. if user.name is None: self.retry = False raise PresentableError( xhtml["Anonymous user has no password."]) # Check if msg has been set and act upon accordingly msg = req.args.msg if msg is not None: self.retry = msg is not PasswordMessage.SUCCESS raise PresentableError(xhtml[passwordStr[msg]])
def getResource(self, resourceId: str) -> ResourceBase: try: return self.resourceDB[resourceId] except KeyError: raise PresentableError(xhtml.p['Resource ', xhtml.b[resourceId], ' does not exist (anymore).'])
async def process(self, req: Request[ProductDefIdArgs], user: User ) -> None: productDefId = req.args.id frameworkDB = self.frameworkDB productDefDB = self.productDefDB try: productDef = productDefDB[productDefId] except KeyError: raise PresentableError(xhtml[ 'Product ', xhtml.b[ productDefId ], ' does not exist.' ]) producers = [] consumers = [] for frameworkId, framework in frameworkDB.items(): if productDefId in framework.getInputs(): consumers.append(frameworkId) if productDefId in framework.getOutputs(): producers.append(frameworkId) graphBuilder = ExecutionGraphBuilder( 'graph', products=[productDef], frameworks=(frameworkDB[fid] for fid in producers + consumers), ) # pylint: disable=attribute-defined-outside-init self.productDef = productDef self.producers = producers self.consumers = consumers self.graph = graphBuilder
async def process(self, req: Request[FrameworkIdArgs], user: User ) -> None: frameworkId = req.args.id frameworkDB = self.frameworkDB productDefDB = self.productDefDB taskDefDB = self.taskDefDB try: framework = frameworkDB[frameworkId] except KeyError: raise PresentableError(xhtml[ 'Framework ', xhtml.b[ frameworkId ], ' does not exist.' ]) taskDefs = list(taskDefsUsingFramework(taskDefDB, frameworkId)) productIds = framework.getInputs() | framework.getOutputs() graphBuilder = ExecutionGraphBuilder( 'graph', products=(productDefDB[pid] for pid in productIds), frameworks=[framework] ) # pylint: disable=attribute-defined-outside-init self.taskDef = framework self.children = taskDefs self.graph = graphBuilder
def _checkState(self) -> None: args = self.args framework = args.framework if framework == '': raise PresentableError(xhtml.p[ 'Please select a framework.' ]) else: try: parent = self.frameworkDB[framework] except KeyError: raise PresentableError(xhtml.p[ f'Framework "{framework}" does not exist (anymore).' ]) checkParamState(args, parent) checkResourceRequirementsState(self.resTypeDB, args)
async def process(self, req: Request['Notifications_POST.Arguments'], user: User) -> None: args = req.args action = args.action smtpRelay = args.smtpRelay mailSender = args.mailSender if action is Actions.CANCEL: page = cast(Notifications_POST, self.page) raise Redirect(page.getParentURL(req.args)) elif action is Actions.TEST: # pylint: disable=attribute-defined-outside-init recipient = args.mailRecipient if not recipient: raise PresentableError( xhtml.p(class_='notice')[ 'Please enter a recipient address ' 'to send the test-email to']) self.mailTestTime = time.localtime() try: addresses: Iterable[Tuple[bytes, int, bytes]] numOk_, addresses = await sendTestMail( smtpRelay, mailSender, args.mailRecipient) except Exception as ex: raise PresentableError( xhtml.p(class_='notice') [f'Sending test mail failed: {ex}']) self.mailTestResult = tuple( (address.decode(errors='replace'), f"{resp.decode(errors='replace')} ({code:d})") for address, code, resp in addresses) elif action is Actions.SAVE: if mailSender and not reMailAddress.match(mailSender): raise PresentableError( xhtml.p(class_='notice') ['Mail sender ', xhtml.code[mailSender], ' does not look like an e-mail address.']) self.project.setMailConfig(args.mailNotification, smtpRelay, mailSender) else: assert False, action
async def process(self, req: Request[ScheduleIdArgs], user: User ) -> None: scheduleId = req.args.id try: # pylint: disable=attribute-defined-outside-init self.scheduled = self.scheduleDB[scheduleId] except KeyError: raise PresentableError(xhtml[ 'Schedule ', xhtml.b[ scheduleId ], ' does not exist.' ])
async def process(self, req: Request['AddUser_POST.Arguments'], user: User ) -> None: if req.args.action is Actions.CANCEL: page = cast(AddUser_POST, self.page) raise Redirect(page.getCancelURL(req.args)) elif req.args.action is Actions.ADD: userDB = self.userDB # Validate input. userName = req.args.user if not userName: raise PresentableError(xhtml['User name cannot be empty.']) # Authenticate currently logged-in operator. reqUserName = user.name if reqUserName is not None: operatorCred = Credentials(reqUserName, req.args.loginpass) try: user_ = await authenticateUser(userDB, operatorCred) except LoginFailed as ex: raise PresentableError(xhtml[ 'Operator authentication failed%s.' % ( ': ' + str(ex) if str(ex) else '' ) ]) # Create new user account. try: roles = uiRoleToSet(req.args.role) addUserAccount(userDB, userName, roles) except ValueError as ex: raise PresentableError(xhtml[f'{ex}.']) # Create a password reset token for the new account. # pylint: disable=attribute-defined-outside-init self.token = resetPassword(userDB, userName, self.tokenDB) else: assert False, req.args.action
async def process(self, req: Request[TaskDefIdArgs], user: User) -> None: taskDefId = req.args.id try: taskDef = self.taskDefDB[taskDefId] except KeyError: raise PresentableError(xhtml['Task Definition ', xhtml.b[taskDefId], ' does not exist.']) # pylint: disable=attribute-defined-outside-init self.taskDef = taskDef self.configs = list(configsUsingTaskDef(self.configDB, taskDefId))
def checkResourceRequirementsState( resTypeDB: ResTypeDB, args: ResourceRequirementsArgsMixin) -> None: args = cast(_ResourceRequirementsArgs, args) if args.type.count(taskRunnerResourceTypeName) != 1: # Even though the UI can only produce one Task Runner entry, # we should never trust the client to enforce that. raise PresentableError( xhtml.p[f'There must be exactly one Task Runner ' f'(resource type "{taskRunnerResourceTypeName}").']) if not len(args.ref) == len(args.type) == len(args.caps): raise PresentableError(xhtml.p['Unequal number of ref/type/caps args']) usedRefs: Set[str] = set() for ref, resType in zip(args.ref, args.type): if resType == '': continue # Check whether reference name is valid. if not ref: raise PresentableError( xhtml.p['Empty resource reference name is not allowed.']) if resType == taskRunnerResourceTypeName: if ref != taskRunnerResourceRefName: raise PresentableError( xhtml.p[f'The Task Runner resource reference must be ' f'named "{taskRunnerResourceRefName}", ' f'got "{ref}" instead.']) else: try: checkWrapperVarName(ref) except KeyError as ex: raise PresentableError( xhtml.p[f'Invalid resource reference name "{ref}": ', xhtml.b[str(ex.args[0])], '.']) # Check whether reference name is unique. if ref in usedRefs: raise PresentableError( xhtml.p[f'Duplicate resource reference name "{ref}".']) usedRefs.add(ref) if resType not in resTypeDB: raise PresentableError( xhtml.p[f'Resource type "{resType}" does not exist (anymore).'] )
async def process(self, req: Request['ConfigDetails_GET.Arguments'], user: User ) -> None: configId = req.args.configId configDB = self.configDB frameworkDB = self.frameworkDB productDefDB = self.productDefDB scheduleDB = self.scheduleDB try: config = configDB[configId] except KeyError: raise PresentableError(xhtml[ 'Configuration ', xhtml.b[ configId ], ' does not exist.' ]) frameworkIds: List[str] = [] productIds: Set[str] = set() for task in config.getTasks(): framework = task.getFramework() if not framework.getId() in frameworkIds: frameworkIds.append(framework.getId()) productIds |= framework.getInputs() productIds |= framework.getOutputs() graphBuilder = ExecutionGraphBuilder( 'graph', products=(productDefDB[pid] for pid in productIds), frameworks=(frameworkDB[fid] for fid in frameworkIds), ) scheduleIds = tuple( scheduleId for scheduleId, schedule in scheduleDB.items() if configId in schedule.getMatchingConfigIds(configDB) ) # pylint: disable=attribute-defined-outside-init self.config = config self.graph = graphBuilder self.scheduleIds = scheduleIds
async def process(self, req: Request['UserDetails_GET.Arguments'], user: User ) -> None: infoUserName = req.args.user try: infoUser = self.userDB[infoUserName] except KeyError: raise PresentableError(xhtml[ 'User ', xhtml.b[ infoUserName ], ' does not exist.' ]) jobDB = self.jobDB jobs = runQuery( [ ValueFilter('owner', infoUserName, jobDB), KeySorter.forDB(['recent'], jobDB) ], jobDB )[ : self.visibleJobs] # pylint: disable=attribute-defined-outside-init self.infoUser = infoUser self.jobs = jobs
def _checkState(self) -> None: args = self.args # Check max job count. if args.maxjobs <= 0: raise PresentableError(xhtml.p[ f'The value for multiple jobs limit ({args.maxjobs:d}) ' f'is invalid; it must be a positive integer.']) # Check timezone. # Since timezone is selected from a drop-down list, under normal # circumstances it will always be valid. timezone = decodeTimezone(args.timezone) knownZones = getKnownTimezones() if knownZones and timezone not in knownZones: raise PresentableError( xhtml.p[f'Unknown timezone "{timezone}".']) # Check site filters. siteFilters = [] for siteFilter in args.embedcustom.split(): if ';' in siteFilter: # Semicolon is used as a separator in the CSP header. raise PresentableError(xhtml.p[ f'Illegal character ";" in site filter "{siteFilter}"'] ) try: url = urlparse(siteFilter) if not url.scheme: # Force scheme-less location to be parsed as a netloc; # otherwise urlparse() treats it as a relative path. url = urlparse('//' + siteFilter) # Force sanity check of port number. _ = url.port except ValueError as ex: raise PresentableError( xhtml.p[f'Invalid site filter "{siteFilter}": {ex}']) for name in ('path', 'params', 'query', 'fragment', 'username', 'password'): if getattr(url, name): raise PresentableError(xhtml.p[ f'Site filter "{siteFilter}" contains {name}, ' f'which is not supported']) scheme = url.scheme netloc = url.netloc if scheme and netloc: siteFilters.append(f'{scheme}://{netloc}') elif scheme: siteFilters.append(f'{scheme}:') elif netloc: siteFilters.append(netloc) else: # Note: I don't know what filter would parse like this, # but handle it just in case. raise PresentableError(xhtml.p[ f'Site filter "{siteFilter}" contains no information']) if not siteFilters and args.embed is EmbeddingPolicy.CUSTOM: raise PresentableError( xhtml.p['Custom embedding policy cannot be empty']) siteFilterStr = ' '.join(siteFilters) if siteFilterStr != args.embedcustom: raise ArgsCorrected(args, embedcustom=siteFilterStr)