snapi 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ 0.0.2
2
+ -- Added SinatraExtension
3
+ 0.0.1
4
+ -- Capability Declaration DSL
5
+ -- Funcation Argument Validation
6
+ -- Pass valid function name + args hash to
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sinatra'
4
+ gem 'sinatra-contrib'
5
+
6
+ group :development, :test do
7
+ gem 'rspec'
8
+ gem 'pry'
9
+ end
10
+
data/Gemfile.lock ADDED
@@ -0,0 +1,47 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ backports (3.3.4)
5
+ coderay (1.0.9)
6
+ diff-lcs (1.2.4)
7
+ method_source (0.8.2)
8
+ multi_json (1.8.0)
9
+ pry (0.9.12.2)
10
+ coderay (~> 1.0.5)
11
+ method_source (~> 0.8)
12
+ slop (~> 3.4)
13
+ rack (1.5.2)
14
+ rack-protection (1.5.0)
15
+ rack
16
+ rack-test (0.6.2)
17
+ rack (>= 1.0)
18
+ rspec (2.14.1)
19
+ rspec-core (~> 2.14.0)
20
+ rspec-expectations (~> 2.14.0)
21
+ rspec-mocks (~> 2.14.0)
22
+ rspec-core (2.14.5)
23
+ rspec-expectations (2.14.3)
24
+ diff-lcs (>= 1.1.3, < 2.0)
25
+ rspec-mocks (2.14.3)
26
+ sinatra (1.4.3)
27
+ rack (~> 1.4)
28
+ rack-protection (~> 1.4)
29
+ tilt (~> 1.3, >= 1.3.4)
30
+ sinatra-contrib (1.4.1)
31
+ backports (>= 2.0)
32
+ multi_json
33
+ rack-protection
34
+ rack-test
35
+ sinatra (~> 1.4.0)
36
+ tilt (~> 1.3)
37
+ slop (3.4.6)
38
+ tilt (1.4.1)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ pry
45
+ rspec
46
+ sinatra
47
+ sinatra-contrib
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # Snapi
2
+
3
+ Snapi is a modular API functionality definition tool.
4
+
5
+ ## Installation
6
+
7
+ This hasn't been put on Rubygems yet so you still have to build the gem
8
+ manually for now.
9
+
10
+ ```sh
11
+ git clone git@github.com:pwnieexpress/snapi.git
12
+ cd snapi
13
+ gem build snapi.gemspec
14
+ gem install snapi
15
+ ```
16
+ This has only been used on `1.9.3` but is should run fine on `1.9+` and `2.x+`
17
+
18
+ ## Usage
19
+
20
+ Simple Exmaple:
21
+
22
+ ```ruby
23
+ require 'snapi'
24
+ require 'json'
25
+
26
+ class ScannerLibrary
27
+ def self.scan(args)
28
+ #
29
+ # _| _ _|_|_ _ _|_|_ o __ _
30
+ # (_|(_) |_| |(/_ |_| | | | |(_|
31
+ # __|
32
+ end
33
+ end
34
+
35
+ class Scanner
36
+ include Snapi::Capability
37
+ function :scan do |fn|
38
+ fn.argument :target do |arg|
39
+ arg.required true
40
+ arg.list true
41
+ arg.type :string
42
+ arg.format :address
43
+ end
44
+ fn.argument :port do |arg|
45
+ arg.type :string
46
+ end
47
+ fn.return :structured
48
+ end
49
+ library ScannerLibrary
50
+ end
51
+ ```
52
+
53
+ ## Sinatra Extension
54
+
55
+ A Sinatra application can be extended with this functionality as follows.
56
+
57
+ ```ruby
58
+ class MyAPi < Sinatra::Base
59
+ register Sinatra::Namespace
60
+
61
+ namespace Snapi.capability_root do
62
+ register Snapi::SinatraExtension
63
+ end
64
+ end
65
+ ```
66
+
67
+ When loaded this application will offer the following routes:
68
+
69
+ ```
70
+ # /plugins/
71
+ # /plugins/scanner/
72
+ # /plugins/scanner/scan/
73
+ ```
74
+
75
+ ## Project Goals & Name
76
+
77
+ I literally woke up in the middle of the night with the idea for a modular
78
+ Sinatra Api plugin system and wrote down the name "Snapi" which is a
79
+ contraction of "Sinatra Api". It might better be spelled "SnApi" but I prefer
80
+ "Snapi" because it snake cases to `snapi`, not `sn_api`. I like the concept of
81
+ snap-in modularity.
82
+
83
+ As of now the only real behavior is that of declaring functions and arguments
84
+ for a class as a way of doing meta-programming funcationality / arrity
85
+ disclosure.
86
+
87
+ The ultimate goal being for an API to be able to define itself dynamically and
88
+ dislose that functionality to remote systems and even do things like:
89
+
90
+ * Dynamic Form Generation
91
+ * API generation (TODO `snapi generate hosts plugin:crud`)
92
+ * Self Documentation
data/lib/snapi.rb ADDED
@@ -0,0 +1,31 @@
1
+ # include all the things!
2
+ require "snapi/validator"
3
+ require "snapi/errors"
4
+ require "snapi/argument"
5
+ require "snapi/function"
6
+ require "snapi/capability"
7
+ require "snapi/version"
8
+
9
+ module Snapi
10
+ @@capabilities = {}
11
+
12
+ def self.capabilities
13
+ @@capabilities || {}
14
+ end
15
+
16
+ def self.register_capability(klass)
17
+ @@capabilities[klass.namespace] = klass
18
+ end
19
+
20
+ def self.valid_capabilities
21
+ @@capabilities.keys
22
+ end
23
+
24
+ def self.capability_root
25
+ "/plugins/?"
26
+ end
27
+ end
28
+
29
+ # This depends on Snapi module being defined as above
30
+ require "snapi/sinatra_extension"
31
+
@@ -0,0 +1,148 @@
1
+ module Snapi
2
+ # Arguments are an intrinsic part of Capability and Function declaration
3
+ # in that Capabilities are made up of Functions and Functions may have
4
+ # one or more Arguments defined for them.
5
+ #
6
+ # Arguments are a code representation of a meta-programming way of defining
7
+ # what arguments or paramater SHOULD or MUST be passed to a method
8
+ # which represents a function on the Capabilities library class
9
+ class Argument
10
+
11
+ # @attributes tracks the various meta-attributes which can be
12
+ # set for the argument record. This initializer simply sets that
13
+ # value as
14
+ def initialize
15
+ @attributes = {}
16
+ end
17
+
18
+ # Allow the record to behave like a hash by giving access to @attributes
19
+ # via [] getter
20
+ #
21
+ # @params key, key to query @attributes with
22
+ def [](key)
23
+ @attributes[key]
24
+ end
25
+
26
+ # Allow the record to behave like a hash by giving access to @attributes
27
+ # via []= setter
28
+ #
29
+ # Validates the key requested to set is included in the valid_attributes
30
+ # white list and then uses the uses the various setter methods below to
31
+ # set the value.
32
+ #
33
+ # @param key, attribute name
34
+ # @param value, value to set
35
+ def []=(key, value)
36
+ raise InvalidArgumentAttributeError unless valid_attributes.include?(key)
37
+ send(key, value)
38
+ end
39
+
40
+ # Get the @attributes hash
41
+ #
42
+ # @returns Hash
43
+ def attributes
44
+ @attributes
45
+ end
46
+
47
+ # Whitelist of attribute names
48
+ #
49
+ # @returns Array of Symbols
50
+ def valid_attributes
51
+ [:default_value, :format, :list, :required, :type, :values]
52
+ end
53
+
54
+ # DSL Setter
55
+ # Set a default value for the argument if one is not provided
56
+ #
57
+ # @param val, Value to use in case one isn't provided,
58
+ def default_value(val)
59
+ raise InvalidStringError unless val.class == String
60
+ @attributes[:default_value] = val
61
+ end
62
+
63
+ # DSL Setter
64
+ # Set a format the check string types against using the
65
+ # format_types outlined in Snapi::Validator
66
+ #
67
+ # @param format, Symbol of format to match against
68
+ def format(format)
69
+ raise InvalidFormatError unless Validator.valid_regex_format?(format)
70
+ @attributes[:format] = format
71
+ end
72
+
73
+ # DSL Setter
74
+ # Is the argument a list of options? If true it will be assumed that
75
+ # this argument will be an array of objects of the sort set as type
76
+ #
77
+ # @param bool, Boolean value
78
+ def list(bool)
79
+ raise InvalidBooleanError unless [true,false].include? bool
80
+ @attributes[:list] = bool
81
+ end
82
+
83
+ # DSL Setter
84
+ # Is the argument a required?
85
+ #
86
+ # @param bool, Boolean value
87
+ def required(bool)
88
+ raise InvalidBooleanError unless [true,false].include? bool
89
+ @attributes[:required] = bool
90
+ end
91
+
92
+ # DSL Setter
93
+ # What type of value is this argument. This will impact the way in which
94
+ # this argument value gets validated later on
95
+ #
96
+ # Valid types are: :boolean, :enum, :string, :number, :timestamp
97
+ #
98
+ # @param type, Symbol indicating type
99
+ def type(type)
100
+ valid_types = [:boolean, :enum, :string, :number, :timestamp]
101
+ raise InvalidTypeError unless valid_types.include?(type)
102
+ @attributes[:type] = type
103
+ end
104
+
105
+ # DSL Setter
106
+ # What are the values that can be selected? This only applies to :enum typed arguments
107
+ # and allow the argument to define a list of valid values to select from for this.
108
+ #
109
+ # In a form this would map to a select box. Alternative to using a :string argument
110
+ # with a format to validate against.
111
+ #
112
+ # Basically creates a whitelist of values for this argument.
113
+ #
114
+ # @param values, Array
115
+ def values(values)
116
+ raise InvalidValuesError unless values.class == Array
117
+ @attributes[:values] = values
118
+ end
119
+
120
+ # Check if a value provided will suffice for the way
121
+ # this argument is defined.
122
+ #
123
+ # @param input, Just about anything...
124
+ # @returns Boolean. true if valid
125
+ def valid_input?(input)
126
+ case @attributes[:type]
127
+ when :boolean
128
+ [true,false].include?(input)
129
+ when :enum
130
+ raise MissingValuesError unless @attributes[:values]
131
+ raise InvalidValuesError unless @attributes[:values].class == Array
132
+
133
+ @attributes[:values].include?(input)
134
+ when :string
135
+ format = @attributes[:format] || :anything
136
+ Validator.valid_input?(format, input)
137
+ when :number
138
+ [Integer, Fixnum].include?(input.class)
139
+ when :timestamp
140
+ # TODO timestamp pending
141
+ raise PendingBranchError
142
+ else
143
+ false
144
+ end
145
+ end
146
+ end
147
+ end
148
+
@@ -0,0 +1,130 @@
1
+ module Snapi
2
+ # This class is exists as a way of definining an API capability
3
+ # using a handy DSL to define functions, their arguments, arity,
4
+ # validations and return types.
5
+ module Capability
6
+
7
+ # When this module is included into a given Class
8
+ # this will wbe run. It will extend the clas with
9
+ # the methods found in the the Snapi::Capability::ClassMethods
10
+ # module and register the class in question with the Snapi
11
+ # module.
12
+ #
13
+ # @params klass, Class
14
+ def self.included(klass)
15
+ klass.extend(ClassMethods)
16
+ Snapi.register_capability(klass)
17
+ end
18
+
19
+ module ClassMethods
20
+ # modify the class to track @functions and @library_class
21
+ # at the class level
22
+ class << self
23
+ attr_accessor :functions, :library_class
24
+ end
25
+
26
+ # Getter to query the internally tracked list of supported
27
+ # functions.
28
+ #
29
+ # @returns Hash, @functions or new Hash
30
+ def functions
31
+ @functions || {}
32
+ end
33
+
34
+ # Getter to query the class which is responsible for having
35
+ # methods which map to the names of the functions.
36
+ #
37
+ # @returns Class, @library_class || self
38
+ def library_class
39
+ @library_class || self
40
+ end
41
+
42
+ # DSL setter to add a function to the @functions hash
43
+ #
44
+ # @params name, Name of function being defined
45
+ # @params Block to modify function
46
+ # @returns Hash of function data
47
+ def function(name)
48
+ raise InvalidFunctionNameError unless Validator.valid_input?(:snapi_function_name, name)
49
+ fn = Function.new
50
+ if block_given?
51
+ yield(fn)
52
+ end
53
+ @functions ||= {}
54
+ @functions[name] = fn
55
+ end
56
+
57
+ # DSL Setter to define the ruby class (or module) which
58
+ # contains methods that should map to the function names
59
+ # in the @functions hash
60
+ #
61
+ # @params klass, Class which default to self
62
+ # @returns Class
63
+ def library(klass=self)
64
+ @library_class = klass
65
+ end
66
+
67
+
68
+ # Convert the class name to a snake-cased symbol namespace
69
+ # representation of class name for use in namespacing
70
+ #
71
+ # @returns Symbol, snake_cased
72
+ def namespace
73
+ self.name
74
+ .split('::').last
75
+ .scan(/([A-Z]+[a-z0-9]+)/)
76
+ .flatten.map(&:downcase)
77
+ .join("_")
78
+ .to_sym
79
+ end
80
+
81
+ # Helper to conver the class itself to a hash representation
82
+ # including the functions hash keyed off the namespace
83
+ #
84
+ # @returns Hash
85
+ def to_hash
86
+ fn_hash = {}
87
+ functions.each {|k,v| fn_hash[k] = v.to_hash } if functions
88
+ {
89
+ self.namespace => fn_hash
90
+ }
91
+ end
92
+
93
+ # Helper to check if a function call to the capability would
94
+ # be valid with the given arguments
95
+ #
96
+ # @param function Symbolic name to reference the function out of the @functions hash
97
+ # @param args hash of arguments
98
+ # @returns Boolean
99
+ def valid_function_call?(function, args)
100
+ return false unless functions[function]
101
+ functions[function].valid_args?(args)
102
+ end
103
+
104
+ # Accepts arguments and sends the args to the library class
105
+ # version of the function assuming that the function has been declared
106
+ # and the library class also includes that function
107
+ #
108
+ # @param function Symbolic name to reference the function out of the @functions hash
109
+ # @param args hash of arguments
110
+ # @returns God only knows, my friend, God only know
111
+ def run_function(function,args)
112
+ raise InvalidFunctionCallError unless valid_function_call?(function, args)
113
+ raise LibraryClassMissingFunctionError unless valid_library_class?
114
+ library_class.send(function,args)
115
+ end
116
+
117
+ # Test if the set library class is valid based on mapping
118
+ # the keys of the @functions hash against the methods available
119
+ # to the @library_class
120
+ #
121
+ # @returns Boolean, true if @library_class offers all the methods needed
122
+ def valid_library_class?
123
+ self.functions.keys.each do |function_name|
124
+ return false unless library_class.methods.include?(function_name)
125
+ end
126
+ true
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,30 @@
1
+ module Snapi
2
+ #
3
+ #
4
+ # _________________ ___________ _______
5
+ # | ___| ___ \ ___ \ _ | ___ \___ / |
6
+ # | |__ | |_/ / |_/ / | | | |_/ / / /| |
7
+ # | __|| /| /| | | | / / / | |
8
+ # | |___| |\ \| |\ \\ \_/ / |\ \./ /__|_|
9
+ # \____/\_| \_\_| \_|\___/\_| \_\_____(_)
10
+ #
11
+ #
12
+ #
13
+ #
14
+ InvalidArgumentAttributeError = Class.new(StandardError)
15
+ InvalidBooleanError = Class.new(StandardError)
16
+ InvalidCapabilityError = Class.new(StandardError)
17
+ InvalidFormatError = Class.new(StandardError)
18
+ InvalidFunctionCallError = Class.new(StandardError)
19
+ InvalidFunctionNameError = Class.new(StandardError)
20
+ InvalidReturnTypeError = Class.new(StandardError)
21
+ InvalidStringError = Class.new(StandardError)
22
+ InvalidTypeError = Class.new(StandardError)
23
+ InvalidValuesError = Class.new(StandardError)
24
+ LibraryClassMissingFunctionError = Class.new(StandardError)
25
+ MissingValuesError = Class.new(StandardError)
26
+
27
+
28
+ # TODO remove
29
+ PendingBranchError = Class.new(StandardError)
30
+ end
@@ -0,0 +1,73 @@
1
+ module Snapi
2
+ # Functions are a core part of capability declaration as a
3
+ # capability is basically a collection of functions.
4
+ #
5
+ # Functions take arguments and return a pre-defined type of
6
+ # data structure.
7
+ #
8
+ # Right now Functions are structs which accept arguments
9
+ # and return_type messages as well as a few DSL methods
10
+ # to help define them dynamically
11
+ #
12
+ Function = Struct.new(:arguments, :return_type ) do
13
+
14
+
15
+ # DSL setter to define a the meta information for an
16
+ # argument for the Function
17
+ #
18
+ # Yields the argument to a given block for nice defition
19
+ #
20
+ # @params name
21
+ def argument(name)
22
+ arg = Argument.new
23
+ if block_given?
24
+ yield(arg)
25
+ end
26
+ self.arguments = {} if self.arguments == nil
27
+ self.arguments[name] = arg
28
+ end
29
+
30
+ # DSL Setter to define the type of data returned by this function
31
+ #
32
+ # TODO currently allows types of 'none', 'raw' or 'structured' but
33
+ # should support 'structured[stucture_type']
34
+ #
35
+ # @params type of returned data
36
+ def return(type)
37
+ valid = %w{ none raw structured }.include? type.to_s
38
+ raise InvalidReturnTypeError unless valid
39
+
40
+ self.return_type = type
41
+ end
42
+
43
+ # Hash representation of function including argument meta-data
44
+ # as a hash.
45
+ #
46
+ # @returns Hash representation of Function
47
+ def to_hash
48
+ args_hash = {}
49
+ arguments.each { |k,v| args_hash[k] = v.attributes } if arguments
50
+ { return_type: return_type }.merge args_hash
51
+ end
52
+
53
+ # This method accepts a hash (as from a web request) which it
54
+ # uses to compare against its argument definitions in order to
55
+ # do some upfront validation before trying to run the function
56
+ # names as defined in the library_class
57
+ #
58
+ # TODO: build up an errors hash to disclose what the issue is
59
+ #
60
+ # @params args Hash
61
+ # @returns Boolean
62
+ def valid_args?(args={})
63
+ valid = false
64
+ arguments.each do |name, argument|
65
+ if argument[:required]
66
+ return false if args[name] == nil
67
+ end
68
+ valid = argument.valid_input?(args[name])
69
+ end
70
+ return valid
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require 'sinatra/contrib'
3
+
4
+ module Snapi
5
+ # TODO document, test, make more robust
6
+ module SinatraExtension
7
+ extend Sinatra::Extension
8
+
9
+ get "/?" do
10
+ JSON.generate(Snapi.capabilities)
11
+ end
12
+
13
+ get "/:capability/?" do
14
+ @capability = params.delete("capability").to_sym
15
+
16
+ unless Snapi.valid_capabilities.include?(@capability)
17
+ raise InvalidCapabilityError
18
+ end
19
+
20
+ JSON.generate(Snapi.capabilities[@capability].to_hash)
21
+ end
22
+
23
+ get "/:capability/:function/?" do
24
+ @capability = params.delete("capability").to_sym
25
+ @function = params.delete("function").to_sym
26
+
27
+ unless Snapi.valid_capabilities.include?(@capability)
28
+ raise InvalidCapabilityError
29
+ end
30
+
31
+ unless Snapi.capabilities[@capability].valid_function_call?(@function,params)
32
+ raise InvalidFunctionCallError
33
+ end
34
+
35
+ # TODO add response_wrapper which ensures that the return data from the
36
+ # function matches the type declared in the capabilities function defitition
37
+ response = Snapi.capabilities[@capability].run_function(@function,params)
38
+ response.class == String ? response : JSON.generate(response)
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,133 @@
1
+ module Snapi
2
+
3
+ # Helpful module to check strings against named Regexp patterns which
4
+ # are stored in here.
5
+ module Validator
6
+
7
+ # Core method of the module which attempts to check if a provided
8
+ # string matches any of the regex's as identified by the key
9
+ #
10
+ # @params key, Symbol key which maps to one of the keys in the validation_regex method below
11
+ # @params string, String to check
12
+ # @returns Boolean, true if string checks out
13
+ def valid_input?(key,string)
14
+ raise InvalidFormatError unless valid_regex_format?(key)
15
+
16
+ boolarray = validation_regex[key].map do |regxp|
17
+ (string =~ regxp) == 0 ? true : false
18
+ end
19
+
20
+ return true if boolarray.include?(true)
21
+ false
22
+ end
23
+
24
+ # A helper to get get the keys off the validation_regex hash
25
+ #
26
+ # @returns Array of symbols
27
+ def format_types
28
+ validation_regex.keys
29
+ end
30
+
31
+ # A helper to validate that the requested symbols is a valid
32
+ # type of format we can check against
33
+ #
34
+ # @param format, Symbol to check
35
+ # @returns Boolean, true if the requested format is in format_types array
36
+ def valid_regex_format?(format)
37
+ format_types.include?(format)
38
+ end
39
+
40
+ # A helper dictionary which returns and array of valid Regexp patterns
41
+ # in exchange for a valid key
42
+ #
43
+ # @returns Hash, dictionary of symbols and Regexp arrays
44
+ def validation_regex
45
+ {
46
+ :address => [HOSTNAME_REGEX, DOMAIN_REGEX, IP_V4_REGEX, IP_V6_REGEX],
47
+ :anything => [/.*/],
48
+ :bool => [TRUEFALSE_REGEX],
49
+ :command => [SIMPLE_COMMAND_REGEX],
50
+ :cron => [CRON_REGEX],
51
+ :gsm_adapter => [ADAPTER_REGEX],
52
+ :hostname => [HOSTNAME_REGEX],
53
+ :interface => [INTERFACE_REGEX],
54
+ :ip => [IP_V4_REGEX, IP_V6_REGEX],
55
+ :ipv6 => [IP_V6_REGEX],
56
+ :ipv4 => [IP_V4_REGEX],
57
+ :mac => [MAC_REGEX],
58
+ :snapi_function_name => [SNAPI_FUNCTION_NAME],
59
+ :string => [STRING_REGEX],
60
+ :on_off => [ON_OFF_REGEX],
61
+ :port => [PORT_REGEX],
62
+ :uri => [URI_REGEX],
63
+ :username => [HOSTNAME_REGEX],
64
+ :password => [NUM_LETTERS_SP_CHARS]
65
+ }
66
+ end
67
+
68
+ module_function :valid_input?, :validation_regex, :format_types, :valid_regex_format?
69
+
70
+ ############################################################################
71
+ # ______ _____ _ _ _____ _ ______
72
+ # | ___ \ ___| | | | _ | | | _ \
73
+ # | |_/ / |__ | |_| | | | | | | | | |
74
+ # | ___ \ __|| _ | | | | | | | | |
75
+ # | |_/ / |___| | | \ \_/ / |___| |/ /
76
+ # \____/\____/\_| |_/\___/\_____/___( )
77
+ # |/
78
+ #
79
+ # _____ _ _ _____ ______ ___ _____ _____ ___________ _ _ _____
80
+ # |_ _| | | || ___| | ___ \/ _ \_ _|_ _| ___| ___ \ \ | |/ ___|
81
+ # | | | |_| || |__ | |_/ / /_\ \| | | | | |__ | |_/ / \| |\ `--.
82
+ # | | | _ || __| | __/| _ || | | | | __|| /| . ` | `--. \
83
+ # | | | | | || |___ | | | | | || | | | | |___| |\ \| |\ |/\__/ /
84
+ # \_/ \_| |_/\____/ \_| \_| |_/\_/ \_/ \____/\_| \_\_| \_/\____/
85
+ #
86
+ #
87
+ #
88
+ # Note base on lib/shells/gsm.rb
89
+ ADAPTER_REGEX = /^(unlocked_gsm|verizon_virgin_mobile|tmobile)$/
90
+ #
91
+ # Note set in lib/shells/scheduler.rb
92
+ CRON_REGEX = /^(1_minute|5_minutes|15_minutes|60_minutes)$/
93
+ #
94
+ # Source:
95
+ SIMPLE_COMMAND_REGEX = /^([a-zA-Z0-9\.\s\-\_\/\\\,\=]+)*$/i
96
+ #
97
+ # Source: RFC952, RFC1034
98
+ HOSTNAME_REGEX = /^[a-z0-9-]{1,63}$/i
99
+ #
100
+ # Source: RFC952, RFC1034, does not take max length into account
101
+ DOMAIN_REGEX = /^([a-z0-9-]{1,63}\.)*[a-z0-9-]{1,63}$/i
102
+ #
103
+ # validate the name for a function name
104
+ SNAPI_FUNCTION_NAME = /[a-z0-9_]/
105
+ #
106
+ INTERFACE_REGEX = /^([a-z]+)+([0-9]+)+$/i
107
+ #
108
+ # IPV4 Source: http://answers.oreilly.com/topic/318-how-to-match-ipv4-addresses-with-regular-expressions/
109
+ IP_V4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
110
+ #
111
+ # IPV6 Source: https://gist.github.com/294476
112
+ IP_V6_REGEX = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
113
+ #
114
+ # Mac Source: http://www.ruby-forum.com/topic/139401
115
+ MAC_REGEX = /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/i
116
+ #
117
+ # SIMPLE ON / OFF (CHECKBOXES)
118
+ ON_OFF_REGEX = /^(on|off)$/i
119
+ #
120
+ # Source:
121
+ STRING_REGEX = /^([a-zA-Z0-9\.\-\_]+)*$/i
122
+ #
123
+ # TRUE OR FALSE
124
+ TRUEFALSE_REGEX = /^(true|false)$/i
125
+ #
126
+ # mmmmmmmm
127
+ PORT_REGEX = /^(6[0-5][0-5][0-3][0-5]|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[1-9])$/
128
+ #
129
+ NUM_LETTERS_SP_CHARS = /^[\w\[\]\!\@\#\$\%\^\&\*\(\)\{\}\:\;\<\>\+\-]*$/i
130
+ #
131
+ URI_REGEX = /https?:\/\/[\S]+/
132
+ end
133
+ end
@@ -0,0 +1,3 @@
1
+ module Snapi
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Snapi::Argument do
4
+ it "is a class" do
5
+ subject.class.class.should == Class
6
+ end
7
+
8
+ it "has an attributes hash" do
9
+ subject.attributes.should == {}
10
+ end
11
+
12
+ it "responds to hash style queries" do
13
+ subject[:does_not_exist].should == nil
14
+ end
15
+
16
+ it "responds to hash style queries" do
17
+ subject[:default_value] = "some value"
18
+ subject[:default_value].should == subject.attributes[:default_value]
19
+ end
20
+
21
+ it "protects from illegal keys being set" do
22
+ begin
23
+ subject[:dude_why_would_you_do_this] = "WHO CARES WHAT THIS IS BECAUSE IT WILL NOT GET SET"
24
+ rescue => error
25
+ error.class.should == Snapi::InvalidArgumentAttributeError
26
+ end
27
+ end
28
+
29
+ it "can have default_value set" do
30
+ subject.default_value("test").should == "test"
31
+ end
32
+
33
+ it "can have required set" do
34
+ subject.required(true).should == true
35
+ end
36
+
37
+ it "can have list set" do
38
+ subject.list(true).should == true
39
+ end
40
+
41
+ it "can have type set" do
42
+ subject.type(:string).should == :string
43
+ end
44
+
45
+ it "can return a hash of its own options" do
46
+ subject.attributes.keys.sort.should == []
47
+ end
48
+
49
+ describe "can validated types such as" do
50
+ it ":boolean" do
51
+ a = Snapi::Argument.new
52
+ a.type :boolean
53
+ a.valid_input?(true).should == true
54
+ a.valid_input?(false).should == true
55
+ a.valid_input?(String).should == false
56
+ a.valid_input?("String").should == false
57
+ a.valid_input?({}).should == false
58
+ a.valid_input?([]).should == false
59
+ end
60
+
61
+ it ":string" do
62
+ a = Snapi::Argument.new
63
+ a.type :string
64
+ a.valid_input?(10.0).should == false
65
+ a.valid_input?(100).should == false
66
+ a.valid_input?(false).should == false
67
+ a.valid_input?([]).should == false
68
+ a.valid_input?({}).should == false
69
+ a.valid_input?(String).should == false
70
+ a.valid_input?("String").should == true
71
+ a.valid_input?(true).should == false
72
+ end
73
+
74
+ it ":string with format" do
75
+ a = Snapi::Argument.new
76
+ a.type :string
77
+ a.format :ip
78
+ a.valid_input?(10.0).should == false
79
+ a.valid_input?(100).should == false
80
+ a.valid_input?("192.168.1.1").should == true
81
+ a.valid_input?(false).should == false
82
+ a.valid_input?([]).should == false
83
+ a.valid_input?({}).should == false
84
+ a.valid_input?("String").should == false
85
+ a.valid_input?(String).should == false
86
+ a.valid_input?(true).should == false
87
+ end
88
+
89
+ it ":enum with values" do
90
+ a = Snapi::Argument.new
91
+ a.type :enum
92
+ a.values [:a,:b,:c]
93
+ a.valid_input?(10.0).should == false
94
+ a.valid_input?(100).should == false
95
+ a.valid_input?(:a).should == true
96
+ a.valid_input?(:b).should == true
97
+ a.valid_input?(:c).should == true
98
+ a.valid_input?(false).should == false
99
+ a.valid_input?([]).should == false
100
+ a.valid_input?({}).should == false
101
+ a.valid_input?("String").should == false
102
+ a.valid_input?(String).should == false
103
+ a.valid_input?(true).should == false
104
+ end
105
+
106
+ it ":number" do
107
+ a = Snapi::Argument.new
108
+ a.type :number
109
+ a.valid_input?(100000000).should == true
110
+ a.valid_input?(10.0).should == false
111
+ a.valid_input?(100).should == true
112
+ a.valid_input?(:a).should == false
113
+ a.valid_input?(:b).should == false
114
+ a.valid_input?(:c).should == false
115
+ a.valid_input?(false).should == false
116
+ a.valid_input?([]).should == false
117
+ a.valid_input?({}).should == false
118
+ a.valid_input?("String").should == false
119
+ a.valid_input?(String).should == false
120
+ a.valid_input?(true).should == false
121
+ end
122
+
123
+ it ":timestamp" do
124
+ pending
125
+ end
126
+ end
127
+ end
128
+
@@ -0,0 +1,160 @@
1
+ require "spec_helper"
2
+
3
+ describe Snapi::BasicCapability do
4
+ describe "has a namespace which" do
5
+ it "can convert its class name into a route namespace" do
6
+ subject.class.namespace.should == :basic_capability
7
+ end
8
+
9
+ it "passes this to inherited BasicCapability objects" do
10
+ LadyRainicornAndPrinceMonochromocorn = Class.new(subject.class)
11
+ LadyRainicornAndPrinceMonochromocorn.namespace.should == :lady_rainicorn_and_prince_monochromocorn
12
+ end
13
+
14
+ it "can return a hash representation of itself" do
15
+ class PrinceLemonGrab < Snapi::BasicCapability
16
+ function :summon_zombies do |fn|
17
+ fn.return :raw
18
+ end
19
+ end
20
+ lemon_grab = {:prince_lemon_grab => {:summon_zombies => { :return_type => :raw}}}
21
+
22
+ PrinceLemonGrab.to_hash.should == lemon_grab
23
+ end
24
+ end
25
+
26
+ describe "DSL" do
27
+ it "can take a function" do
28
+ class PrincessBubblegum < Snapi::BasicCapability
29
+ function :create_candy_person do |fn|
30
+ fn.argument :candy_base do |arg|
31
+ arg.default_value "sugar"
32
+ arg.format :anything
33
+ arg.list true
34
+ arg.required true
35
+ arg.type :enum
36
+ end
37
+ fn.return :structured
38
+ end
39
+ end
40
+
41
+ expected_return = {
42
+ :create_candy_person => {
43
+ :return_type =>:structured,
44
+ :candy_base => {
45
+ :default_value => "sugar",
46
+ :format => :anything,
47
+ :required => true,
48
+ :list => true,
49
+ :type => :enum,
50
+ }
51
+ }
52
+ }
53
+ PrincessBubblegum.to_hash.should == { PrincessBubblegum.namespace => expected_return}
54
+ end
55
+
56
+ it "doesn't shared functions between inherited classes" do
57
+ class FinnTheHuman < Snapi::BasicCapability
58
+ function :enchyridion do |fn|
59
+ fn.return :raw
60
+ end
61
+ end
62
+ class JakeTheDog < Snapi::BasicCapability
63
+ function :beemo do |fn|
64
+ fn.return :raw
65
+ end
66
+ end
67
+
68
+ FinnTheHuman.functions[:enchyridion].should_not == nil
69
+ FinnTheHuman.functions[:beemo].should == nil
70
+
71
+ JakeTheDog.functions[:enchyridion].should == nil
72
+ JakeTheDog.functions[:beemo].should_not == nil
73
+
74
+ Snapi::BasicCapability.functions.should == {}
75
+
76
+ end
77
+ end
78
+
79
+ describe "tracks a :library class or module which provides methods as defined by the function block" do
80
+ it "defaults to self" do
81
+ class TheLich < Snapi::BasicCapability
82
+ end
83
+ TheLich.library_class.should == TheLich
84
+ end
85
+ it "can be set via self.library " do
86
+ class BillysLittleFriend
87
+ def help_somebody
88
+ end
89
+ end
90
+ class BillyTheHero < Snapi::BasicCapability
91
+ library BillysLittleFriend
92
+ function :help_somebody
93
+ end
94
+ BillyTheHero.library_class.should == BillysLittleFriend
95
+ end
96
+ it "can validate if the library has the valid methods" do
97
+ class BillysLittleFriend
98
+ def self.help_somebody
99
+ end
100
+ end
101
+ class BillyTheHero < Snapi::BasicCapability
102
+ library BillysLittleFriend
103
+ function :help_somebody
104
+ end
105
+ class FrankTheVillain < Snapi::BasicCapability
106
+ library BillysLittleFriend
107
+ function :hurt_somebody
108
+ end
109
+ BillyTheHero.valid_library_class?.should == true
110
+ FrankTheVillain.valid_library_class?.should == false
111
+ end
112
+ end
113
+
114
+ describe "can run a function with a hash of arguments" do
115
+ class IceKing < Snapi::BasicCapability
116
+ function :ice_attack do |fn|
117
+ fn.argument :victim do |arg|
118
+ arg.required true
119
+ arg.type :string
120
+ end
121
+ end
122
+ end
123
+
124
+ it "validates a hash of arguments against a function" do
125
+ IceKing.valid_function_call?(:icicle, {:victim => "Gunther"}).should == false
126
+ IceKing.valid_function_call?(:ice_attack, {}).should == false
127
+ IceKing.valid_function_call?(:ice_attack, {:victim => "Gunther"}).should == true
128
+ end
129
+
130
+ it "raises errors when invalid args or function are sent" do
131
+ begin
132
+ IceKing.run_function(:icicle, {:victim => "Gunther"}).should == false
133
+ rescue => e
134
+ e.class.should == Snapi::InvalidFunctionCallError
135
+ end
136
+ end
137
+
138
+ it "raises errors when the library class does not support the requested library " do
139
+ begin
140
+ IceKing.run_function(:ice_attack, {:victim => "Gunther"}).should == false
141
+ rescue => e
142
+ e.class.should == Snapi::LibraryClassMissingFunctionError
143
+ end
144
+ end
145
+
146
+ it "runs the function if available in the library class" do
147
+ class IceWand
148
+ def self.ice_attack(args={})
149
+ "ZAP #{args[:victim].upcase}!"
150
+ end
151
+ end
152
+
153
+ class IceKing
154
+ library IceWand
155
+ end
156
+
157
+ IceKing.run_function(:ice_attack, {:victim => "Gunther"}).should == "ZAP GUNTHER!"
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Snapi::Function do
4
+ it "can take an argument" do
5
+ subject.argument :test do |arg|
6
+ arg.default_value "test"
7
+ arg.list true
8
+ arg.type :string
9
+ end
10
+
11
+ subject.argument :test2 do |arg|
12
+ arg.default_value "testing more"
13
+ arg.required true
14
+ arg.type :string
15
+ end
16
+
17
+ subject.arguments[:test][:required].should == nil
18
+ subject.arguments[:test2][:required].should == true
19
+
20
+ end
21
+
22
+ it "can validate a ruby hash of keys and values against its arguments" do
23
+ subject.argument :argument do |arg|
24
+ arg.default_value "test"
25
+ arg.required true
26
+ arg.type :string
27
+ end
28
+
29
+ subject.valid_args?({}).should == false
30
+ subject.valid_args?({:not_argument => "test value"}).should == false
31
+ subject.valid_args?({:argument => "test value"}).should == true
32
+ # ignores
33
+ subject.valid_args?({:argument => "test value", :not => :relevant}).should == true
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ require 'rspec'
2
+ require 'pry'
3
+
4
+ require File.expand_path('../../lib/snapi.rb', __FILE__)
5
+
6
+ class Snapi::BasicCapability
7
+ include Snapi::Capability
8
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+ describe "Snapi::Validator" do
3
+ it "is a module" do
4
+ Snapi::Validator.class.should == Module
5
+ end
6
+
7
+ it "includes an array of format types it can validate" do
8
+ Snapi::Validator.format_types.class.should == Array
9
+ end
10
+
11
+ it "can identify valid format types" do
12
+ test_format = Snapi::Validator.format_types.shuffle.first
13
+ Snapi::Validator.valid_regex_format?(test_format).should == true
14
+ end
15
+
16
+ it "can identify invalid format types" do
17
+ test_format = :no_frakin_way_this_key_exists_seriously_dude
18
+ Snapi::Validator.valid_regex_format?(test_format).should == false
19
+ end
20
+
21
+ it "offers an array of regular expressions in exchange for a format type" do
22
+ test_format = Snapi::Validator.format_types.shuffle.first
23
+ v_reg = Snapi::Validator.validation_regex[test_format]
24
+ v_reg.class.should == Array
25
+ v_reg.first.class.should == Regexp
26
+ end
27
+
28
+ it "will attempt to confirm if a string matches the specified regex" do
29
+ Snapi::Validator.valid_input?( :ip, "192.168.10.129" ).should == true
30
+ Snapi::Validator.valid_input?( :ip, "192.168.10.256" ).should == false
31
+ Snapi::Validator.valid_input?( :ip, "Jake the Dog" ).should == false
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gabe Koss
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Modular Capability Disclosure DSL
31
+ email: gabe@pwnieexpress.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/snapi.rb
37
+ - lib/snapi/argument.rb
38
+ - lib/snapi/validator.rb
39
+ - lib/snapi/function.rb
40
+ - lib/snapi/version.rb
41
+ - lib/snapi/sinatra_extension.rb
42
+ - lib/snapi/capability.rb
43
+ - lib/snapi/errors.rb
44
+ - README.md
45
+ - CHANGELOG
46
+ - Gemfile.lock
47
+ - Gemfile
48
+ - spec/basic_capability_spec.rb
49
+ - spec/validator_spec.rb
50
+ - spec/spec_helper.rb
51
+ - spec/function_spec.rb
52
+ - spec/argument_spec.rb
53
+ homepage: https://github.com/pwnieexpress/snapi
54
+ licenses:
55
+ - MIT
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 1.8.25
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Snapi
78
+ test_files: []