def handleCreate(self, confInfo): args = self.callerArgs.data # Sanity checking for app ID: no special chars and shorter than 100 chars appName = self.callerArgs.id if not appName or len(appName) == 0: raise admin.ArgValidationException( _('App folder name is not set.')) if re.search('[^A-Za-z0-9._-]', appName): raise admin.ArgValidationException( _('App folder name cannot contain spaces or special characters.' )) if len(appName) > 100: raise admin.ArgValidationException( _('App folder name cannot be longer than 100 characters.')) kwargs = { 'label': _getFieldValue(args, HTTP_POST_LABEL, appName, maxLen=100), 'visible': _getFieldValue(args, HTTP_POST_VISIBLE, 'true'), 'author': _getFieldValue(args, HTTP_POST_AUTHOR, '', maxLen=100), 'description': _getFieldValue(args, HTTP_POST_DESC, '', maxLen=500), 'configured': _getFieldValue(args, HTTP_POST_CONFIGURED, '0'), } template = _getFieldValue(args, HTTP_POST_TEMPLATE, DEFAULT_TEMPLATE) try: url = appbuilder.createApp(appName, template, **kwargs) appbuilder.addUploadAssets(appName) except splunk.RESTException, e: raise admin.InternalException(e.msg)
def to_python(self, name, value): if value is None: return None int_value = int(str(value).strip()) # Make sure that the value is at least the minimum if self.min_value is not None and int_value < self.min_value: raise admin.ArgValidationException( "The value of '%s' for the '%s' parameter is not valid, it must be at least %s" % (str(value), name, self.min_value)) # Make sure that the value is no greater than the maximum if self.max_value is not None and int_value > self.max_value: raise admin.ArgValidationException( "The value of '%s' for the '%s' parameter is not valid, it must be not be greater than %s" % (str(value), name, self.max_value)) try: return int(str(value).strip()) except ValueError: raise admin.ArgValidationException( "The value of '%s' for the '%s' parameter is not a valid integer" % (str(value), name))
def handleCreate(self, confInfo): logger.debug("In handleCreate") # Refresh self.handleReload() name = self.callerArgs.id args = self.callerArgs.data # Make sure the name is not empty if not name or len(name) == 0: raise admin.ArgValidationException("The stanza name must not be empty") # Make sure the item does not already exist if name in self.readConf("log_review"): raise admin.AlreadyExistsException("A entry already exists for %s" % (name)) # Get the field values # TODO: obtain the values of the fields into Python variables debug = _getFieldValue( args, self.PARAM_DEBUG, default_value='false' ) comment_minimum_length = _getFieldValue( args, self.PARAM_COMMENT_MINIMUM_LENGTH, default_value=self.DEFAULT_COMMENT_LENGTH ) comment_required = _getFieldValue( args, self.PARAM_COMMENT_REQUIRED, default_value=self.DEFAULT_COMMENT_REQUIRED ) ir_header_labels = _getFieldValue( args, self.PARAM_TABLE_LABELS, default_value=self.DEFAULT_TABLE_LABELS ) ir_header_fields = _getFieldValue( args, self.PARAM_TABLE_FIELDS, default_value=self.DEFAULT_TABLE_FIELDS ) ir_attribute_labels = _getFieldValue( args, self.PARAM_ATTRIBUTE_LABELS, default_value=self.DEFAULT_ATTRIBUTE_LABELS ) ir_attribute_fields = _getFieldValue( args, self.PARAM_ATTRIBUTE_FIELDS, default_value=self.DEFAULT_ATTRIBUTE_FIELDS ) # Add the field values to a configuration dictionary (that will be verified) conf = entity.getEntity('configs/conf-log_review', '_new', sessionKey=self.getSessionKey()) conf.namespace = self.appName # always save things to SOME app context. conf.owner = self.context == admin.CONTEXT_APP_AND_USER and self.userName or "-" conf['name'] = name _addToDictIfNonNull(conf, self.PARAM_DEBUG, debug) _addToDictIfNonNull(conf, self.PARAM_COMMENT_MINIMUM_LENGTH, comment_minimum_length) _addToDictIfNonNull(conf, self.DEFAULT_COMMENT_REQUIRED, comment_required) _addToDictIfNonNull(conf, self.DEFAULT_TABLE_LABELS, ir_header_labels) _addToDictIfNonNull(conf, self.DEFAULT_TABLE_FIELDS, ir_header_fields) _addToDictIfNonNull(conf, self.DEFAULT_ATTRIBUTE_LABELS, ir_attribute_labels) _addToDictIfNonNull(conf, self.DEFAULT_ATTRIBUTE_FIELDS, ir_attribute_fields) # Check the configuration try: self.checkConf(conf, name) except InvalidConfigException as e: logger.error( "The configuration for '%s' is invalid and could not be created: %s" % ( name, str(e)) ) raise admin.ArgValidationException( str(e) ) # Write out an update to the config file entity.setEntity(conf, sessionKey=self.getSessionKey()) # Refresh self.handleReload()
def handleEdit(self, confInfo): """ Handles edits to the configuration options """ logger.debug("In handleEdit") # Refresh self.handleReload() name = self.callerArgs.id args = self.callerArgs if name is not None: try: conf = entity.getEntity('configs/conf-log_review', name, sessionKey=self.getSessionKey()) except ResourceNotFound: raise admin.NotFoundException("A log_review setting with the given name '%s' could not be found" % (name)) else: # Stop if no name was provided raise admin.ArgValidationException("No name provided") # Create the resulting configuration that would be persisted if the settings provided are applied for key, val in conf.items(): if key in args.data: conf[key] = args[key][0] if key == admin.EAI_ENTRY_ACL: if val.has_key('app') and val['app'] is not None and len(val['app']) > 0: conf.namespace = val['app'] if val.has_key('owner') and val['owner'] is not None and len(val['owner']) > 0: conf.owner = val['owner'] if conf.namespace is None or len(conf.namespace) == 0: conf.namespace = LogReview.DEFAULT_NAMESPACE if conf.owner is None or len(conf.owner) == 0: conf.owner = LogReview.DEFAULT_OWNER # Check the configuration try: self.checkConf(conf, name) except InvalidConfigException as e: logger.error( "The configuration for '%s' is invalid and could not be edited: %s" % ( name, str(e)) ) raise admin.ArgValidationException( str(e) ) logger.debug("Updating configuration for " + str(name)) entity.setEntity(conf, sessionKey=self.getSessionKey()) ## Reload log_review self.handleReload()
def _getFieldValue(args, fieldName, defaultVal=None, maxLen=None): value = args[fieldName][0] or defaultVal if fieldName in args else defaultVal if value and maxLen and len(value) > maxLen: raise admin.ArgValidationException(i18n.ungettext('App %(fieldName)s cannot be longer than %(maxLen)s character.', 'App %(fieldName)s cannot be longer than %(maxLen)s characters.', maxLen) % {'fieldName' : fieldName, 'maxLen' : maxLen} ) return value
def handleList(self, confInfo): args = self.callerArgs.data path = self.callerArgs.id names = [] allowFolders = allowFiles = True if ALLOW_FOLDER_SELECTION in args: allowFolders = bp.parse_boolean(args[ALLOW_FOLDER_SELECTION][0]) if ALLOW_FILE_SELECTION in args: allowFiles = bp.parse_boolean(args[ALLOW_FILE_SELECTION][0]) if not path: if sys.platform.startswith('win'): import win32api names = [ drive for drive in win32api.GetLogicalDriveStrings().split( '\000') if drive ] path = '' else: path = '/' try: names = sorted(os.listdir(path)) except Exception, e: raise admin.ArgValidationException(e)
def to_python(self, name, value): """ Convert the field to a Python object. Should throw a ArgValidationException if the data is invalid. Arguments: name -- The name of the object, used for error messages value -- The value to convert """ if value is None: raise admin.ArgValidationException("The value for the '%s' parameter cannot be none" % (name)) if len( str(value).strip() ) == 0: raise admin.ArgValidationException("The value for the '%s' parameter cannot be empty" % (name)) return value
def to_python(self, name, value): if value is None: return value elif not self.is_valid_hostname(value): raise admin.ArgValidationException("The value of '%s' for the '%s' parameter is not a valid host name or IP" % ( str(value), name)) return value
def _handleUpdateSnowAccount(self, confInfo, args): settings = ("url", "username", "password") for k in settings: if not args.get(k, None) or not args[k][0]: err_msg = 'ServiceNow "{}" is mandatory'.format(k) _LOGGER.error(err_msg) raise admin.ArgValidationException(err_msg) self._handleUpdateAccount(confInfo, args, "snow_account", settings)
def _handleUpdateOktaAccount(self, confInfo, args): settings = ("endpoint", "token") for k in settings: if not args.get(k, None) or not args[k][0]: err_msg = 'Okta "{}" is mandantory'.format(k) _LOGGER.error(err_msg) raise admin.ArgValidationException(err_msg) self._handleUpdateAccount(confInfo, args, self.okta_endpoint, settings)
def checkConf(self, settings, stanza=None, confInfo=None, onlyCheckProvidedFields=False, existing_settings=None): """ Checks the settings and raises an exception if the configuration is invalid. Arguments: settings -- The settings dictionary the represents the configuration to be checked stanza -- The name of the stanza being checked confInfo -- The confinfo object that was received into the REST handler onlyCheckProvidedFields -- Indicates if we ought to assume that this is only part of the fields and thus should not alert if some necessary fields are missing existing_settings -- The existing settings before the current changes are applied """ # Add all of the configuration items to the confInfo object so that the REST endpoint lists them (even if they are wrong) # We want them all to be listed so that the users can see what the current value is (and hopefully will notice that it is wrong) for key, val in settings.items(): # Add the value to the configuration info if stanza is not None and confInfo is not None: # Handle the EAI:ACLs differently than the normal values if key == 'eai:acl': confInfo[stanza].setMetadata(key, val) elif key in self.valid_params and key not in self.unsaved_params: confInfo[stanza].append(key, val) # Below is a list of the required fields. The entries in this list will be removed as they # are observed. An empty list at the end of the config check indicates that all necessary # fields where provided. required_fields = self.required_params[:] # Check each of the settings for key, val in settings.items(): # Remove the field from the list of required fields try: required_fields.remove(key) except ValueError: pass # Field not available, probably because it is not required # Stop if not all of the required parameters are not provided if onlyCheckProvidedFields == False and len(required_fields) > 0: #stanza != "default" and raise admin.ArgValidationException("The following fields must be defined in the configuration but were not: " + ",".join(required_fields)) # Clean up and validate the parameters cleaned_params = self.convertParams(stanza, settings, False) # Run the general validators for validator in self.general_validators: validator.validate(stanza, cleaned_params, existing_settings) # Remove the parameters that are not intended to be saved for to_remove in self.unsaved_params: if to_remove in cleaned_params: del cleaned_params[to_remove] # Return the cleaned parameters return cleaned_params
def handleCreate(self, confInfo): args = self.callerArgs.data # Sanity checking for app ID: no special chars and shorter than 100 chars appName = self.callerArgs.id if not appName or len(appName) == 0: raise admin.ArgValidationException('App folder name is not set.') if re.search('[^A-Za-z0-9._-]', appName): raise admin.ArgValidationException( 'App folder name cannot contain spaces or special characters.') if len(appName) > 100: raise admin.ArgValidationException( 'App folder name cannot be longer than 100 characters.') kwargs = { 'label': _getFieldValue(args, HTTP_POST_LABEL, appName, maxLen=100), 'visible': _getFieldValue(args, HTTP_POST_VISIBLE, 'true'), 'author': _getFieldValue(args, HTTP_POST_AUTHOR, '', maxLen=100), 'description': _getFieldValue(args, HTTP_POST_DESC, '', maxLen=500), 'configured': _getFieldValue(args, HTTP_POST_CONFIGURED, '0'), 'version': _getFieldValue(args, HTTP_POST_VERSION, '1.0.0') } template = _getFieldValue(args, HTTP_POST_TEMPLATE, DEFAULT_TEMPLATE) if re.match("^\d{1,3}\.\d{1,3}\.\d{1,3}(\s?\w[\w\d]{,9})?$", kwargs['version']) is None: raise admin.ArgValidationException( "Version '%s' is invalid. Use the version format 'major.minor.patch', for example '1.0.0'." % kwargs['version']) try: url = appbuilder.createApp(appName, template, **kwargs) appbuilder.addUploadAssets(appName) except splunk.RESTException as e: raise admin.InternalException(e.msg) confInfo[appName].append('name', appName) for field in kwargs: confInfo[appName].append(field, kwargs[field])
def handleEdit(self, confInfo): # let's make sure this thing exists, first... existing = admin.ConfigInfo() self.handleList(existing) if not self.callerArgs.id in existing: raise admin.ArgValidationException( "Cannot edit '%s', it does not exist." % self.callerArgs.id) self.updateConf("imap", self.callerArgs.id, self.callerArgs.data)
def handleEdit(self, _): # pylint:disable=invalid-name """ After user clicks Save on setup page, take updated parameters, normalize them, and save them somewhere """ data = {k: [s or "" for s in v] for k, v in self.callerArgs.data.items()} server_info = self.service.info() if server_info.get("instance_type") == "cloud": ingest_url = data.get("ingest_url") if ingest_url and ingest_url[0] and not ingest_url[0].startswith("https"): raise admin.ArgValidationException("ingest_url must be https in Splunk Cloud") access_token_list = data.pop("access_token") if not access_token_list: raise admin.ArgValidationException("required access token is missing") self.save_access_token(access_token_list[0]) ssl_verify = data.pop("ssl_verify")
def validateIndexers(cls, indexers): if not indexers: raise admin.ArgValidationException("Indexers list cannot be blank") indexersList = indexers.split(",") hostRE = re.compile( "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])([:][0-9]+)?$" ) ipRE = re.compile( "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])([:][0-9]+)?$" ) for indexer in indexersList: if not (hostRE.match(indexer) or ipRE.match(indexer)): raise admin.ArgValidationException( "Indexer %s is not a valid IP address or domain name" % (indexer))
def validate(self, name, values, existing_settings=None): # Determine if a username and a password were provided password_provided = 'test_password' in values and values[ 'test_password'] is not None and len(values['test_password']) > 0 username_provided = 'test_username' in values and values[ 'test_username'] is not None and len(values['test_username']) > 0 # Warn if a username was provided but a password was not if not password_provided and username_provided: raise admin.ArgValidationException( "A username to test was provided but a password was not") # Warn if a password was provided but a username was not if password_provided and not username_provided: raise admin.ArgValidationException( "A password to test was provided but a username was not") # Use the settings from the values if provided, otherwise, use the old settings if values.get( 'secret', None) is not None and len(str(values['secret']).strip()) > 0: secret = values['secret'] elif existing_settings is not None and existing_settings.get( 'secret', None) is not None and len( existing_settings.get('secret', None).strip()) > 0: secret = existing_settings['secret'] else: secret = None # Test the settings if a test username and password provided if password_provided and username_provided and not self.testSettings( values['server'], secret, values.get('identifier', None), values['test_username'], values['test_password']): logger.info( "Test of credentials failed against the server '%s' for user '%s'" % (values['server'], values['test_username'])) raise admin.ArgValidationException( "Unable to validate credentials against the server '%s' for user '%s'" % (values['server'], values['test_username']))
def to_python(self, name, value): if value in [True, False]: return value elif str(value).strip().lower() in ["true", "1"]: return True elif str(value).strip().lower() in ["false", "0"]: return False raise admin.ArgValidationException("The value of '%s' for the '%s' parameter is not a valid boolean" % (str(value), name))
def to_python(self, name, value): if value is None: return None try: return int(str(value).strip()) except ValueError: raise admin.ArgValidationException( "The value of '%s' for the '%s' parameter is not a valid integer" % (str(value), name))
def to_python(self, name, value): if value is None: return None # Prepare the string so that the proxy type can be matched more reliably t = value.strip().lower() if t not in ["socks4", "socks5", "http", ""]: raise admin.ArgValidationException("The value of '%s' for the '%s' parameter is not a valid proxy type" % ( str(value), name)) return t
def installApp(location, force=False): installer = bundle_paths.BundleInstaller() location = location.strip() try: if location.startswith('http'): req = urllib2.Request(url=location) return installer.install_from_url(req, force) else: return installer.install_from_tar(location, force) except splunk.ResourceNotFound, e: raise admin.ArgValidationException(e.msg)
def _handleUpdateAccount(self, confInfo, args, stanza, settings): account = {} for k in settings: if args.get(k, None): account[k] = args[k][0] confInfo[stanza].append(k, args[k]) conf_mgr = oc.OktaConfManager(scc.getMgmtUri(), self.getSessionKey()) res = conf_mgr.update(self.appName, stanza, account) if not res: _LOGGER.error("Failed to commit settings") raise admin.ArgValidationException("Failed to commit settings")
def _getFieldValue(args, name, default_value=None, max_length=None): '''Get the field value from the argument list.''' # Get the value if defined or the default value if not defined value = args[name][0] or default_value if name in args else default_value # Check the length if value and max_length and len(value) > max_length: raise admin.ArgValidationException( 'App %s cannot be longer than %s character%s.' % (name, max_length, "s" if max_length > 1 else "")) return value
def packageApp(appName, needsMerging=True): appPath = mergeApp(appName) if needsMerging else _getAppPath(appName, True) if not appPath: raise admin.ArgValidationException( _('The app "%s" cannot be found.') % appName) packageDir = _getPackageDir() tarFile = "%s.tar.gz" % appName tarPath = os.path.join(packageDir, tarFile) z = tarfile.open(tarPath, 'w:gz') # walk through files in directory and package them up def _tarWalker(tar, appPath, files, root=appPath): for file in files: file = os.path.join(appPath, file) archiveName = os.path.join( appName, file[len(os.path.commonprefix((root, file))) + 1:]) # skip hidden unix files if os.sep + '.' in archiveName: continue # skip old default dirs if archiveName.startswith(os.path.join(appName, 'default.old.')): continue # set execution permission flag on extension-less files in bin directory if not os.path.isdir(file) and archiveName.startswith( os.path.join(appName, 'bin')): info = tarfile.TarInfo(name=archiveName.replace('\\', '/')) fobj = open(file, 'rb') info.size = os.fstat(fobj.fileno()).st_size info.mtime = os.path.getmtime(file) info.mode = 0755 tar.addfile(info, fileobj=fobj) fobj.close() else: tar.add(file, archiveName, False) os.path.walk(appPath, _tarWalker, z) z.close() # cleanup tmp dir if needsMerging: bundle_paths.safe_remove(appPath) splTarPath = tarPath.replace('tar.gz', 'spl') if os.path.exists(splTarPath): bundle_paths.safe_remove(splTarPath) os.rename(tarPath, splTarPath) url = '%s/static/app-packages/%s' % (_getSplunkdUri(), os.path.basename(splTarPath)) return (url, splTarPath)
def handleRemove(self, confInfo): # let's make sure this thing exists, first... existing = admin.ConfigInfo() self.handleList(existing) if not self.callerArgs.id in existing: raise admin.ArgValidationException( "Cannot remove '%s', it does not exist." % self.callerArgs.id) # now that we're sure, set it to disabled and write it out. settsDict = self.readConf("imap")[self.callerArgs.id] settsDict["disabled"] = "true" self.updateConf("imap", self.callerArgs.id, settsDict)
def handleEdit(self, _): # pylint:disable=invalid-name """ After user clicks Save on setup page, take updated parameters, normalize them, and save them somewhere """ data = {k: [s or "" for s in v] for k, v in self.callerArgs.data.items()} server_info = self.service.info() if server_info.get("instance_type") == "cloud": ingest_url = data.get("ingest_url") if ingest_url and ingest_url[0] and not ingest_url[0].startswith("https"): raise admin.ArgValidationException("ingest_url must be https in Splunk Cloud") access_token_list = data.pop("access_token") if not access_token_list: raise admin.ArgValidationException("required access token is missing") self.save_access_token(access_token_list[0]) # Since we are using a conf file to store parameters, # write them to the [SignalFxConfig] stanza # in splunk-forwarder/local/sfx.conf self.writeConf("sfx", "setupentity", data)
def createApp(appName, template, **kwargs): appPath = _getAppPath(appName) if os.path.exists(appPath): raise admin.AlreadyExistsException( _("App '%s' already exists. Nothing was created.") % appName) if template not in getTemplates(): raise admin.ArgValidationException( _("Template '%s' does not exist.") % template) # Make sure we don't mess the app.conf file - add a backslash at the eol kwargs['description'] = kwargs['description'].replace('\n', '\\\n') # Generate files for the app bundle_paths.maybe_makedirs(appPath) os.chdir(appPath) templatePath = os.path.join(TEMPLATES_PATH, template) for root, dirs, files in os.walk(templatePath): # determine relative path relPath = root[len(templatePath) + 1:] # create subdirs for dir in dirs: bundle_paths.maybe_makedirs(os.path.join(relPath, dir)) # Read template files and apply param values then save for fn in files: try: # use params to create custom file names inFilePath = os.path.join(root, fn) # filter by file type fn, isText = _isTextFile(fn) outFilePath = os.path.join(appPath, relPath, fn) if not isText: comm.copyItem(inFilePath, outFilePath) continue with open(inFilePath, 'r') as f_in: content = f_in.read() content = SafeTemplate(content).substitute(kwargs) with open(outFilePath, 'w') as f_out: f_out.write(content) except: print traceback.print_exc(file=sys.stderr) pass return '%s/app/%s' % (_getSplunkWebUri(), appName)
def handleList(self, confInfo): session_key = self.getSessionKey() if recomm_utils.is_splunk_light(session_key): logger.info('ML lib does not exist in Splunk Light.') raise admin.ArgValidationException('Not supported in Splunk Light') if not recomm_utils.is_ml_lib_included(session_key): logger.info('ML lib does not exist') raise admin.ArgValidationException('ML lib does not exist') results = self.kao.query_items() results = json.loads(results) for result in results: d = confInfo[result['_key']] for key in result.keys(): d.append(key, result[key]) logger.info('list %s recommendation' % (len(results))) return
def handleEdit(self, confInfo): """ After user clicks Save on setup screen, take updated parameters, normalize them, and save them somewhere """ _LOGGER.info("start edit") state_file = op.join(op.dirname(op.abspath(__file__)), ".cred") if not op.exists(state_file): with open(state_file, "w") as f: state = {"url": "None", "proxy_url": "None"} json.dump(state, f) else: with open(state_file) as f: state = json.load(f) args = self.callerArgs.data for arg in self.snow_args: if args.get(arg, None) and args[arg][0] is None: args[arg][0] = "" if "url" in args: if not re.findall("^https", str(args["url"][0])): err_msg = "Provide a URL in the following format: https://example.com" _LOGGER.error(err_msg) raise admin.ArgValidationException(err_msg) self._handleUpdateSnowAccount(confInfo, args) state["url"] = "Done" with open(state_file, "w") as f: json.dump(state, f) if "proxy_url" in args: self._handleUpdateProxyAccount(confInfo, args) state["proxy_url"] = "Done" with open(state_file, "w") as f: json.dump(state, f) if "collection_interval" in args: self._handleUpdateDefaultSettings(confInfo, args) conf.reload_confs(("service_now", ), self.getSessionKey(), scc.getMgmtUri()) if state["url"] == "Done" and state["proxy_url"] == "Done": os.remove(state_file) self._verify_creds() _LOGGER.info("end edit")
def handleCreate(self, confInfo): settings = self.callerArgs.copy() passwdProvided = False if 'password' in self.callerArgs.data and self.callerArgs['password']: passwdProvided = True elif 'xpassword' in self.callerArgs.data and self.callerArgs[ 'xpassword']: passwdProvided = True if not passwdProvided: raise admin.ArgValidationException( "Either password or xpassword must be provided") self.updateConf("imap", self.callerArgs.id, self.callerArgs.data)
def handleCustom(self, confInfo): action = self.customAction actionType = self.requestedAction # Create a package of an application if self.customAction == 'package': appName = self.callerArgs.id try: url, path = appbuilder.packageApp(appName) confInfo['Package'].append('name', appName) confInfo['Package'].append('url', url) confInfo['Package'].append('path', path) except splunk.RESTException, e: raise admin.ArgValidationException(e.msg)