snapi 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []