Skip to content

Commit

Permalink
feat(1-Platform#12,1-Platform#3): changed to uniform plugin loading a…
Browse files Browse the repository at this point in the history
…nd added more checks
  • Loading branch information
akhilmhdh committed Feb 3, 2023
1 parent d3ea096 commit 755a245
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 116 deletions.
66 changes: 16 additions & 50 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,10 @@ import (
"github.com/spf13/viper"
)

type ApiCatalogRule struct {
Options map[string]any
Disable bool
}

type ApiCatalogUserPlugin struct {
File string
Options map[string]any
}

type ApiCatalogConfig struct {
Title string
Rules map[string]ApiCatalogRule
Plugins map[string]ApiCatalogUserPlugin
Rules map[string]pluginmanager.PluginUserOverride
Plugins pluginmanager.PluginConfFile
}

func Run() {
Expand Down Expand Up @@ -67,8 +57,16 @@ func Run() {
}

// loading up the plugins and corresponding rules
pluginManager := pluginmanager.New(fr)
if err := pluginManager.LoadBuiltinPlugin(); err != nil {
pManager := pluginmanager.New(fr, apiType)
if err := pManager.LoadBuiltinPlugin(); err != nil {
log.Fatal(err)
}

if err := pManager.LoadUserPlugins(config.Plugins); err != nil {
log.Fatal(err)
}

if err := pManager.OverrideRules(config.Rules); err != nil {
log.Fatal(err)
}

Expand Down Expand Up @@ -96,49 +94,17 @@ func Run() {
log.Fatal("Error api type not supported: ", apiType)
}

for rule, p := range pluginManager.Rules {
userRuleCfg, ok := config.Rules[rule]
if ok && userRuleCfg.Disable {
for rule, opt := range pManager.Rules {
if opt.Disable {
continue
}

// read original code
rawCode, err := pluginManager.ReadPluginCode(p.File)
if err != nil {
log.Fatal("Failed to : ", err)
}
// babel transpile
code, err := cmp.Transform(rawCode)
rawCode, err := pManager.ReadPluginCode(opt.File)
if err != nil {
log.Fatal("Failed to : ", err)
}

// creating config for each rule because we also want rule name of each score and report setter
runCfg := &compiler.RunConfig{
Type: apiType,
ApiSchema: apiSchemaFile,
SetScore: func(category string, score float32) {
rm.SetScore(rule, reportmanager.Score{Category: category, Value: score})
},
Report: func(body *reportmanager.ReportDef) {
rm.PushReport(rule, *body)
},
}

// execute the code
err = cmp.Run(code, runCfg, userRuleCfg.Options)
if err != nil {
log.Fatal("Error in program: ", err)
}
}

// run user defined plugins
for rule, p := range config.Plugins {
// read original code
rawCode, err := pluginManager.ReadPluginCode(p.File)
if err != nil {
log.Fatal("Failed to : ", err)
}
// babel transpile
code, err := cmp.Transform(rawCode)
if err != nil {
Expand All @@ -158,7 +124,7 @@ func Run() {
}

// execute the code
err = cmp.Run(code, runCfg, p.Options)
err = cmp.Run(code, runCfg, opt.Options)
if err != nil {
log.Fatal("Error in program: ", err)
}
Expand Down
106 changes: 71 additions & 35 deletions internal/cli/pluginmanager/plugin_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,32 @@ type Reader interface {
// Builtin will packaged as zip
// Installed on runtime
type PluginManager struct {
Rules map[string]struct{ File string }
Reader Reader
Rules map[string]*PluginRule
Reader Reader
ApiType string
}

type PluginConfig struct {
Rules []PluginConfigRule `yaml:"rules" `
// these are the
type PluginRule struct {
File string
Disable bool
Options map[string]any
}

type PluginConfigRule struct {
Name string `yaml:"name"`
File string `yam:"file"`
type PluginUserOverride struct {
Disable *bool `json:"omitempty" yaml:"omitempty" toml:"omitempty"`
Options map[string]any `json:"omitempty" yaml:"omitempty" toml:"omitempty"`
}

func New(fr Reader) *PluginManager {
type PluginConfFile struct {
Rules map[string]PluginRule
}

func New(fr Reader, apiType string) *PluginManager {
return &PluginManager{
Rules: map[string]struct{ File string }{},
Reader: fr,
Rules: make(map[string]*PluginRule),
Reader: fr,
ApiType: apiType,
}
}

Expand All @@ -49,39 +58,66 @@ func getPluginConfFile(files []fs.DirEntry) (string, error) {

func (p *PluginManager) LoadBuiltinPlugin() error {
cwd, _ := os.Getwd()

// TODO(akhilmhdh): In prod mode this should point to "/.apic/plugins"
path := filepath.Clean(filepath.Join(cwd, "./plugins/builtin"))
pluginsDir, err := os.ReadDir(path)
path := filepath.Clean(filepath.Join(cwd, fmt.Sprintf("./plugins/builtin/%s", p.ApiType)))
builtInPlugin, err := os.ReadDir(path)
if err != nil {
log.Fatal("Failed to open builtin plugins dir: ", err)
}

for _, pluginDir := range pluginsDir {
if pluginDir.IsDir() {
pluginName := pluginDir.Name()
pluginFolder := filepath.Join(path, fmt.Sprintf("/%s", pluginName))
pluginFiles, err := os.ReadDir(pluginFolder)
if err != nil {
return err
}
// get plugin config file.
pluginCfgName, err := getPluginConfFile(pluginFiles)
if err != nil {
return err
}
// get plugin config file.
pluginCfgName, err := getPluginConfFile(builtInPlugin)
if err != nil {
return err
}

// load plugin config
cfgFilePath := filepath.Join(path, fmt.Sprintf("/%s/%s", pluginName, pluginCfgName))
var pluginCfg PluginConfig
if err := p.Reader.ReadFile(cfgFilePath, &pluginCfg); err != nil {
return err
}
// load plugin config
cfgFilePath := filepath.Join(path, pluginCfgName)
var pluginCfg PluginConfFile
if err := p.Reader.ReadFile(cfgFilePath, &pluginCfg); err != nil {
return err
}

// load up the rules
for rule, conf := range pluginCfg.Rules {
jsRuleFile := filepath.Join(path, fmt.Sprintf("/%s", conf.File))
p.Rules[rule] = &PluginRule{Disable: conf.Disable, File: jsRuleFile, Options: conf.Options}
}

// load up the rules
for _, r := range pluginCfg.Rules {
jsRuleFile := filepath.Join(pluginFolder, fmt.Sprintf("/%s", r.File))
p.Rules[r.Name] = struct{ File string }{File: jsRuleFile}
return nil
}

func (p *PluginManager) LoadUserPlugins(userPlugins PluginConfFile) error {
// load up the rules
for rule, conf := range userPlugins.Rules {
if _, ok := p.Rules[rule]; ok {
fmt.Printf("Warning: %s is already defined. Overriding it.\n", rule)
}

p.Rules[rule] = &PluginRule{Disable: conf.Disable, File: conf.File, Options: conf.Options}
}

return nil
}

func (p *PluginManager) OverrideRules(userOverrides map[string]PluginUserOverride) error {
for rule, conf := range userOverrides {
if val, ok := p.Rules[rule]; ok {
if conf.Disable != nil {
val.Disable = *conf.Disable
}
if conf.Options != nil {
for i, r := range conf.Options {
if val.Options == nil {
val.Options = make(map[string]any, 0)
}
val.Options[i] = r
}
}
p.Rules[rule] = val
} else {
fmt.Printf("Overriding rule %s not found\n", rule)
}
}

Expand Down
14 changes: 10 additions & 4 deletions plugins/builtin/openapi/config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
rules:
- name: "status_code_check"
status_code_check:
file: "status_code_check.js"
- name: "body_in_get_req"
body_in_get_req:
file: "body_in_get_req.js"
- name: "case_checker"
file: "case_checker.js"
url_case_checker:
file: "url_case_checker.js"
unsafe_url_character_check:
file: "unsafe_url_character_check.js"
url_length:
file: "url_length.js"
req_body_case_checker:
file: "req_body_case_checker.js"
Empty file.
80 changes: 80 additions & 0 deletions plugins/builtin/openapi/req_body_case_checker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const snakeCaseRegex = /^[a-z0-9]+(?:_[a-z0-9]+)*$/;
const camelCaseRegex = /^[a-z]+(?:[A-Z0-9]+[a-z0-9]+[A-Za-z0-9]*)*$/;
const pascalCaseRegex = /^(?:[A-Z][a-z0-9]+)(?:[A-Z]+[a-z0-9]*)*$/;
const kebabCaseRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

function isCamelCase(word) {
return camelCaseRegex.test(word);
}

function isPascalCase(word) {
return pascalCaseRegex.test(word);
}

function isSnakeCase(word) {
return snakeCaseRegex.test(word);
}

function isKebabCase(word) {
return kebabCaseRegex.test(word);
}

function getCaseCheckerFn(type) {
switch (type) {
case "camelcase":
return isCamelCase;
case "snakecase":
return isSnakeCase;
case "pascalcase":
return isPascalCase;
case "kebabcase":
return isKebabCase;
default:
return isCamelCase;
}
}

export default function (config, options = {}) {
let numberOfResponses = 0;
let numbnerOfFalseResponses = 0;
const checkerFn = getCaseCheckerFn(options?.casing);

Object.keys(config.schema.paths).forEach((path) => {
Object.keys(config.schema.paths[path]).forEach((method) => {
(config.schema.paths[path][method].parameters || []).forEach((param) => {
numberOfResponses++;
if (!checkerFn(param.name)) {
numbnerOfFalseResponses++;
config.report({
message: `Invalid casing for ${param.name} of ${param.in}`,
path: path,
method: method,
});
}
});
});
});

Object.keys(config.schema.components.schemas).forEach((schema) => {
Object.keys(config.schema.components.schemas[schema].properties).forEach(
(property) => {
numberOfResponses++;
if (!checkerFn(property)) {
numbnerOfFalseResponses++;
config.report({
message: `Invalid casing for ${property} of schema ${schema}`,
path: "Nil",
method: "Nil",
});
}
}
);
});

// if number goes to negative
const score =
(Math.max(numberOfResponses - numbnerOfFalseResponses, 0) /
numberOfResponses) *
100;
config.setScore("quality", score);
}
31 changes: 31 additions & 0 deletions plugins/builtin/openapi/unsafe_url_character_check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// By this spec: https://perishablepress.com/stop-using-unsafe-characters-in-urls/
const unsafeURLRegex = /^[a-zA-Z0-9{}\/~_-]*$/;

export default function (config) {
let numberOfResponses = 0;
let numbnerOfFalseResponses = 0;

Object.keys(config.schema.paths).forEach((path) => {
numberOfResponses++;
if (!unsafeURLRegex.test(path)) {
numbnerOfFalseResponses++;

// get all methods
const methods = Object.keys(config.schema.paths[path])
.join(", ")
.toUpperCase();
config.report({
message: `URL contains unsafe character`,
path: path,
method: methods,
});
}
});

const score =
(Math.max(numberOfResponses - numbnerOfFalseResponses, 0) /
numberOfResponses) *
100;

config.setScore("quality", score);
}
Loading

0 comments on commit 755a245

Please sign in to comment.