scim2-filter 0.2.0
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.
- checksums.yaml +7 -0
- data/README.md +38 -0
- data/lib/scim2-filter.rb +1 -0
- data/lib/scim2.rb +8 -0
- data/lib/scim2/filter.rb +23 -0
- data/lib/scim2/filter/arel_handler.rb +125 -0
- data/lib/scim2/filter/lexer.rb +15 -0
- data/lib/scim2/filter/lexer.rex +65 -0
- data/lib/scim2/filter/lexer.rex.rb +144 -0
- data/lib/scim2/filter/no_op_handler.rb +27 -0
- data/lib/scim2/filter/parser.racc +108 -0
- data/lib/scim2/filter/parser.rb +43 -0
- data/lib/scim2/filter/parser.tab.rb +327 -0
- data/lib/scim2/filter/simple_handler.rb +109 -0
- metadata +186 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8e97f1535209f959f102bff6a8d05ab2b0be5284f99482a34bcdfe36030c949b
|
4
|
+
data.tar.gz: b69d559219489bfdd64a90af1c6cbc8bbb86576514d83ab985da04ddb0250dee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4aee232a4ffb1bc1a6138d96702d10c7d53face887c05522671d2079fef7e87846b1db9cbc184e976100094de729b0d38cb7db9d5b473db94811c96f4d51fe9e
|
7
|
+
data.tar.gz: 1f1af70c8fbb720e9b8b38ecc95b9d4e33cc5d7565393852598c661ca6770e35dd17a34b0fec24dc4f2ea9474fea74adddf5091e04bf5d8e021f937c5077200f
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# SCIM2 Filter
|
2
|
+
|
3
|
+
* README: https://github.com/david-mccullars/scim2-filter
|
4
|
+
* Documentation: http://www.rubydoc.info/github/david-mccullars/scim2-filter
|
5
|
+
* Bug Reports: https://github.com/david-mccullars/scim2-filter/issues
|
6
|
+
|
7
|
+
|
8
|
+
## Status
|
9
|
+
|
10
|
+
[](https://badge.fury.io/rb/scim2-filter)
|
11
|
+
[](https://travis-ci.org/david-mccullars/scim2-filter)
|
12
|
+
[](https://codeclimate.com/github/david-mccullars/scim2-filter)
|
13
|
+
|
14
|
+
|
15
|
+
## Description
|
16
|
+
|
17
|
+
|
18
|
+
## Features
|
19
|
+
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
```
|
24
|
+
gem install scim2-filter
|
25
|
+
```
|
26
|
+
|
27
|
+
## Requirements
|
28
|
+
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
|
33
|
+
## License
|
34
|
+
|
35
|
+
MIT. See the {file:LICENSE} file.
|
36
|
+
|
37
|
+
|
38
|
+
## References
|
data/lib/scim2-filter.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'scim2'
|
data/lib/scim2.rb
ADDED
data/lib/scim2/filter.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Scim2
|
2
|
+
##
|
3
|
+
#
|
4
|
+
# RFC7644 SCIM (System for Cross-domain Identity Management) 2.0 defines
|
5
|
+
# mechanisms to query resources. This includes an optional `filter` parameter
|
6
|
+
# which allows for comprehensive filtering of resources based on various
|
7
|
+
# criteria. The structure and syntax for the `filter` parameter is defined in
|
8
|
+
# Section 3.4.2.2 of RFC7644. This module contains classes to support the
|
9
|
+
# validation and parsing of the `filter` parameter as well as its translation
|
10
|
+
# into Arel nodes for use in querying a database.
|
11
|
+
#
|
12
|
+
# @see https://tools.ietf.org/html/rfc7644#section-3.4.2.2
|
13
|
+
##
|
14
|
+
module Filter
|
15
|
+
|
16
|
+
autoload :ArelHandler, 'scim2/filter/arel_handler'
|
17
|
+
autoload :Lexer, 'scim2/filter/lexer'
|
18
|
+
autoload :NoOpHandler, 'scim2/filter/no_op_handler'
|
19
|
+
autoload :Parser, 'scim2/filter/parser'
|
20
|
+
autoload :SimpleHandler, 'scim2/filter/simple_handler'
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Scim2
|
2
|
+
module Filter
|
3
|
+
##
|
4
|
+
# Implementation of parser handler which translates SCIM 2.0 filters into AREL.
|
5
|
+
# In order to do this, instances of this class will need to be passed the mapping
|
6
|
+
# of attribute names to columns/AREL.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # userName sw "J"
|
10
|
+
#
|
11
|
+
# mapping = {
|
12
|
+
# userName: User.arel_table[:name],
|
13
|
+
# }
|
14
|
+
#
|
15
|
+
# # "users"."name" LIKE 'J%'
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # urn:ietf:params:scim:schemas:core:2.0:User:userType ne "Employee" and not (emails.value co "example.com" or emails.value co "example.org")
|
19
|
+
#
|
20
|
+
# mapping = {
|
21
|
+
# userType: User.arel_table[:type],
|
22
|
+
# emails: {
|
23
|
+
# value: User.arel_table[:email],
|
24
|
+
# },
|
25
|
+
# }
|
26
|
+
#
|
27
|
+
# # "users"."type" != 'Employee' AND NOT ("users"."email" LIKE '%example.com' OR "users"."email" LIKE '%example.org%')
|
28
|
+
#
|
29
|
+
# @note Nested filters (e.g. `emails[type eq "work"]` are not supported at this
|
30
|
+
# time and will result in an `ArgumentError`
|
31
|
+
##
|
32
|
+
class ArelHandler
|
33
|
+
|
34
|
+
attr_reader :arel_mapping
|
35
|
+
|
36
|
+
def initialize(arel_mapping)
|
37
|
+
@arel_mapping = arel_mapping
|
38
|
+
end
|
39
|
+
|
40
|
+
# Handle NOT filters (e.g. `not (color eq "red")`)
|
41
|
+
# @param filter [Hash<Symbol, Object>] the internal filter being NOT'ed
|
42
|
+
# @return [Hash<Symbol, Object>]
|
43
|
+
def on_not_filter(filter, context:)
|
44
|
+
filter.not
|
45
|
+
end
|
46
|
+
|
47
|
+
# Handle basic attribute comparison filters (e.g. `preference.color eq "red"`)
|
48
|
+
# @param attribute_path [Array<Symbol>] the attribute name(s) being filtered on, split by `.`
|
49
|
+
# @param value [Object] the value being compared against
|
50
|
+
# @param op [Object] the comparison operator (e.g. `:eq`)
|
51
|
+
# @param schema [String] schema namespace of the attribute
|
52
|
+
# @return [Hash<Symbol, Object>]
|
53
|
+
def on_attribute_filter(attribute_path, value, context:, op:, schema: nil)
|
54
|
+
arel = lookup_arel(attribute_path)
|
55
|
+
case op
|
56
|
+
when :eq
|
57
|
+
arel.eq(value)
|
58
|
+
when :ne
|
59
|
+
arel.not_eq(value)
|
60
|
+
when :co
|
61
|
+
arel.matches("%#{value}%")
|
62
|
+
when :sw
|
63
|
+
arel.matches("#{value}%")
|
64
|
+
when :ew
|
65
|
+
arel.matches("%#{value}")
|
66
|
+
when :gt
|
67
|
+
arel.gt(value)
|
68
|
+
when :ge
|
69
|
+
arel.gteq(value)
|
70
|
+
when :lt
|
71
|
+
arel.lt(value)
|
72
|
+
when :le
|
73
|
+
arel.lteq(value)
|
74
|
+
when :pr
|
75
|
+
arel.not_eq(nil)
|
76
|
+
else
|
77
|
+
raise Racc::ParseError, "invalid attribute operand #{op.inspect} with argument #{value.inspect}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Handle logical filters (e.g. `name.givenName sw "D" AND title co "VP"`)
|
82
|
+
# @param filter1 [Hash<Symbol, Object>] the left-hand side filter
|
83
|
+
# @param filter2 [Hash<Symbol, Object>] the right-hand side filter
|
84
|
+
# @param op [Object] the logical operator (e.g. `AND`)
|
85
|
+
# @return [Hash<Symbol, Object>]
|
86
|
+
def on_logical_filter(filter1, filter2, context:, op:)
|
87
|
+
case op
|
88
|
+
when :and
|
89
|
+
filter1.and(filter2)
|
90
|
+
when :or
|
91
|
+
filter1.or(filter2)
|
92
|
+
else
|
93
|
+
raise Racc::ParseError, "invalid logical operand #{op.inspect}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Handle nested filters (e.g. `emails[type eq "work"]`)
|
98
|
+
# @param attribute_path [Array<Symbol>] the attribute name(s) being filtered on, split by `.`
|
99
|
+
# @param filter [Hash<Symbol, Object>] the nested filter inside the backets
|
100
|
+
# @param schema [String] schema namespace of the attribute
|
101
|
+
# @return [Hash<Symbol, Object>]
|
102
|
+
def on_nested_filter(attribute_path, filter, context:, schema: nil)
|
103
|
+
raise ArgumentError, 'Nested attributes are not currently supported for AREL'
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
# Looks up the arel object from the mapping according to the given attribute path
|
109
|
+
# @param attribute_path [Array<Symbol>] the attribute name(s) being filtered on, split by `.`
|
110
|
+
# @return [Object] the object returned by the mapping
|
111
|
+
def lookup_arel(attribute_path)
|
112
|
+
arel = arel_mapping.dig(*attribute_path)
|
113
|
+
case arel
|
114
|
+
when NilClass
|
115
|
+
raise ArgumentError, "Attribute #{attribute_path.join(',').inspect} not found in mapping"
|
116
|
+
when Arel::Predications
|
117
|
+
arel
|
118
|
+
else
|
119
|
+
raise ArgumentError, "Mapping for attribute #{attribute_path.join(',').inspect} is not a valid arel object"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'lexer.rex'
|
2
|
+
|
3
|
+
module Scim2
|
4
|
+
module Filter
|
5
|
+
##
|
6
|
+
# Implements a SCIM2 compliant lexer for query filters.
|
7
|
+
# This class is responsible for producing and emitting
|
8
|
+
# tokens for consumption by a parser. It is not
|
9
|
+
# intended to use directly.
|
10
|
+
##
|
11
|
+
class Lexer < ::Racc::Parser
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Scim2::Filter::Lexer
|
2
|
+
|
3
|
+
macro
|
4
|
+
EQ [eE][qQ]
|
5
|
+
NE [nN][eE]
|
6
|
+
GT [gG][tT]
|
7
|
+
GE [gG][eE]
|
8
|
+
LT [lL][tT]
|
9
|
+
LE [lL][eE]
|
10
|
+
CO [cC][oO]
|
11
|
+
SW [sS][wW]
|
12
|
+
EW [eE][wW]
|
13
|
+
PR [pP][rR]
|
14
|
+
|
15
|
+
AND [aA][nN][dD]
|
16
|
+
OR [oO][rR]
|
17
|
+
NOT [nN][oO][tT]
|
18
|
+
|
19
|
+
LPAREN \(
|
20
|
+
RPAREN \)
|
21
|
+
LBRACKET \[
|
22
|
+
RBRACKET \]
|
23
|
+
COLON :
|
24
|
+
DOT \.
|
25
|
+
|
26
|
+
DIGIT \d
|
27
|
+
ALPHA [a-zA-Z]
|
28
|
+
NAMECHAR [a-zA-Z0-9_-]
|
29
|
+
SCHEMACHAR [a-zA-Z0-9:\._-]
|
30
|
+
ESCAPEDSTR "(?:[^"\\]|\\.)*"
|
31
|
+
|
32
|
+
rule
|
33
|
+
# Attribute Operators
|
34
|
+
\s{EQ}\s { [:EQ, :eq] }
|
35
|
+
\s{NE}\s { [:NE, :ne] }
|
36
|
+
\s{GT}\s { [:GT, :gt] }
|
37
|
+
\s{GE}\s { [:GE, :ge] }
|
38
|
+
\s{LT}\s { [:LT, :lt] }
|
39
|
+
\s{LE}\s { [:LE, :le] }
|
40
|
+
\s{CO}\s { [:CO, :co] }
|
41
|
+
\s{SW}\s { [:SW, :sw] }
|
42
|
+
\s{EW}\s { [:EW, :ew] }
|
43
|
+
\s{PR} { [:PR, :pr] }
|
44
|
+
|
45
|
+
# Logical Operators
|
46
|
+
\s{AND}\s { [:AND, :and] }
|
47
|
+
\s{OR}\s { [:OR, :or] }
|
48
|
+
{NOT}\s* { [:NOT, :not] } # NOTE: RFC7644 (Section 3.4.2) seems to specify no space, but the example contradicts it
|
49
|
+
|
50
|
+
# Grouping Operators
|
51
|
+
{LPAREN} { [:LPAREN, text] }
|
52
|
+
{RPAREN} { [:RPAREN, text] }
|
53
|
+
{LBRACKET} { [:LBRACKET, text] }
|
54
|
+
{RBRACKET} { [:RBRACKET, text] }
|
55
|
+
|
56
|
+
# Other
|
57
|
+
null { [:NULL, nil] }
|
58
|
+
(?:true|false) { [:BOOLEAN, text == 'true'] }
|
59
|
+
{DIGIT}+ { [:NUMBER, text.to_i] }
|
60
|
+
{ESCAPEDSTR} { [:STRING, text.undump] }
|
61
|
+
({ALPHA}+{COLON}{SCHEMACHAR}+){COLON} { [:SCHEMA, @ss.captures.first] }
|
62
|
+
{ALPHA}{NAMECHAR}* { [:ATTRNAME, text.to_sym] }
|
63
|
+
{DOT} { [:DOT, text] }
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#--
|
2
|
+
# DO NOT MODIFY!!!!
|
3
|
+
# This file is automatically generated by rex 1.0.7
|
4
|
+
# from lexical definition file "lib/scim2/filter/lexer.rex".
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'racc/parser'
|
8
|
+
class Scim2::Filter::Lexer < Racc::Parser
|
9
|
+
require 'strscan'
|
10
|
+
|
11
|
+
class ScanError < StandardError ; end
|
12
|
+
|
13
|
+
attr_reader :lineno
|
14
|
+
attr_reader :filename
|
15
|
+
attr_accessor :state
|
16
|
+
|
17
|
+
def scan_setup(str)
|
18
|
+
@ss = StringScanner.new(str)
|
19
|
+
@lineno = 1
|
20
|
+
@state = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def action
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
|
27
|
+
def scan_str(str)
|
28
|
+
scan_setup(str)
|
29
|
+
do_parse
|
30
|
+
end
|
31
|
+
alias :scan :scan_str
|
32
|
+
|
33
|
+
def load_file( filename )
|
34
|
+
@filename = filename
|
35
|
+
File.open(filename, "r") do |f|
|
36
|
+
scan_setup(f.read)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def scan_file( filename )
|
41
|
+
load_file(filename)
|
42
|
+
do_parse
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def next_token
|
47
|
+
return if @ss.eos?
|
48
|
+
|
49
|
+
# skips empty actions
|
50
|
+
until token = _next_token or @ss.eos?; end
|
51
|
+
token
|
52
|
+
end
|
53
|
+
|
54
|
+
def _next_token
|
55
|
+
text = @ss.peek(1)
|
56
|
+
@lineno += 1 if text == "\n"
|
57
|
+
token = case @state
|
58
|
+
when nil
|
59
|
+
case
|
60
|
+
when (text = @ss.scan(/\s[eE][qQ]\s/))
|
61
|
+
action { [:EQ, :eq] }
|
62
|
+
|
63
|
+
when (text = @ss.scan(/\s[nN][eE]\s/))
|
64
|
+
action { [:NE, :ne] }
|
65
|
+
|
66
|
+
when (text = @ss.scan(/\s[gG][tT]\s/))
|
67
|
+
action { [:GT, :gt] }
|
68
|
+
|
69
|
+
when (text = @ss.scan(/\s[gG][eE]\s/))
|
70
|
+
action { [:GE, :ge] }
|
71
|
+
|
72
|
+
when (text = @ss.scan(/\s[lL][tT]\s/))
|
73
|
+
action { [:LT, :lt] }
|
74
|
+
|
75
|
+
when (text = @ss.scan(/\s[lL][eE]\s/))
|
76
|
+
action { [:LE, :le] }
|
77
|
+
|
78
|
+
when (text = @ss.scan(/\s[cC][oO]\s/))
|
79
|
+
action { [:CO, :co] }
|
80
|
+
|
81
|
+
when (text = @ss.scan(/\s[sS][wW]\s/))
|
82
|
+
action { [:SW, :sw] }
|
83
|
+
|
84
|
+
when (text = @ss.scan(/\s[eE][wW]\s/))
|
85
|
+
action { [:EW, :ew] }
|
86
|
+
|
87
|
+
when (text = @ss.scan(/\s[pP][rR]/))
|
88
|
+
action { [:PR, :pr] }
|
89
|
+
|
90
|
+
when (text = @ss.scan(/\s[aA][nN][dD]\s/))
|
91
|
+
action { [:AND, :and] }
|
92
|
+
|
93
|
+
when (text = @ss.scan(/\s[oO][rR]\s/))
|
94
|
+
action { [:OR, :or] }
|
95
|
+
|
96
|
+
when (text = @ss.scan(/[nN][oO][tT]\s*/))
|
97
|
+
action { [:NOT, :not] } # NOTE: RFC7644 (Section 3.4.2) seems to specify no space, but the example contradicts it
|
98
|
+
|
99
|
+
when (text = @ss.scan(/\(/))
|
100
|
+
action { [:LPAREN, text] }
|
101
|
+
|
102
|
+
when (text = @ss.scan(/\)/))
|
103
|
+
action { [:RPAREN, text] }
|
104
|
+
|
105
|
+
when (text = @ss.scan(/\[/))
|
106
|
+
action { [:LBRACKET, text] }
|
107
|
+
|
108
|
+
when (text = @ss.scan(/\]/))
|
109
|
+
action { [:RBRACKET, text] }
|
110
|
+
|
111
|
+
when (text = @ss.scan(/null/))
|
112
|
+
action { [:NULL, nil] }
|
113
|
+
|
114
|
+
when (text = @ss.scan(/(?:true|false)/))
|
115
|
+
action { [:BOOLEAN, text == 'true'] }
|
116
|
+
|
117
|
+
when (text = @ss.scan(/\d+/))
|
118
|
+
action { [:NUMBER, text.to_i] }
|
119
|
+
|
120
|
+
when (text = @ss.scan(/"(?:[^"\\]|\\.)*"/))
|
121
|
+
action { [:STRING, text.undump] }
|
122
|
+
|
123
|
+
when (text = @ss.scan(/([a-zA-Z]+:[a-zA-Z0-9:\._-]+):/))
|
124
|
+
action { [:SCHEMA, @ss.captures.first] }
|
125
|
+
|
126
|
+
when (text = @ss.scan(/[a-zA-Z][a-zA-Z0-9_-]*/))
|
127
|
+
action { [:ATTRNAME, text.to_sym] }
|
128
|
+
|
129
|
+
when (text = @ss.scan(/\./))
|
130
|
+
action { [:DOT, text] }
|
131
|
+
|
132
|
+
|
133
|
+
else
|
134
|
+
text = @ss.string[@ss.pos .. -1]
|
135
|
+
raise ScanError, "can not match: '" + text + "'"
|
136
|
+
end # if
|
137
|
+
|
138
|
+
else
|
139
|
+
raise ScanError, "undefined state: '" + state.to_s + "'"
|
140
|
+
end # case state
|
141
|
+
token
|
142
|
+
end # def _next_token
|
143
|
+
|
144
|
+
end # class
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Scim2
|
2
|
+
module Filter
|
3
|
+
##
|
4
|
+
# A no-op handler implementation which does nothing with parse events. Its
|
5
|
+
# primary is for validation where the parsed data is not needed.
|
6
|
+
##
|
7
|
+
class NoOpHandler
|
8
|
+
|
9
|
+
# Handle NOT filters (e.g. `not (color eq "red")`)
|
10
|
+
# @return [NilClass]
|
11
|
+
def on_not_filter(filter, context:); end
|
12
|
+
|
13
|
+
# Handle basic attribute comparison filters (e.g. `preference.color eq "red"`)
|
14
|
+
# @return [NilClass]
|
15
|
+
def on_attribute_filter(attribute_path, value, context:, op:, schema: nil); end
|
16
|
+
|
17
|
+
# Handle logical filters (e.g. `name.givenName sw "D" AND title co "VP"`)
|
18
|
+
# @return [NilClass]
|
19
|
+
def on_logical_filter(filter1, filter2, context:, op:); end
|
20
|
+
|
21
|
+
# Handle sub filters (e.g. `emails[type eq "work"]`)
|
22
|
+
# @return [NilClass]
|
23
|
+
def on_nested_filter(attribute_path, filter, context:, schema: nil); end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class Scim2::Filter::Parser
|
2
|
+
|
3
|
+
token EQ NE GT GE LT LE CO SW EW PR AND OR NOT LPAREN RPAREN LBRACKET RBRACKET
|
4
|
+
token NULL BOOLEAN NUMBER STRING ATTRNAME SCHEMA DOT
|
5
|
+
|
6
|
+
rule
|
7
|
+
#
|
8
|
+
# We must separate OR and AND filter rules in order to ensure the order of operations
|
9
|
+
# is properly followed. Specifically, the following two filters are the same:
|
10
|
+
#
|
11
|
+
# w AND x OR y AND z
|
12
|
+
# (w AND x) OR (y AND z)
|
13
|
+
#
|
14
|
+
# Separating the rules the parser will aggressively gobble up AND rules first, leaving
|
15
|
+
# OR rules for the very last as it bubbles back up the recursion stack
|
16
|
+
#
|
17
|
+
filter
|
18
|
+
: non_or_filter
|
19
|
+
| filter OR non_or_filter
|
20
|
+
{
|
21
|
+
filter1, op, filter2 = val
|
22
|
+
result = handler.on_logical_filter(filter1, filter2, op: op, context: result)
|
23
|
+
}
|
24
|
+
|
25
|
+
non_or_filter
|
26
|
+
: non_boolean_filter
|
27
|
+
| non_or_filter AND non_boolean_filter
|
28
|
+
{
|
29
|
+
filter1, op, filter2 = val
|
30
|
+
result = handler.on_logical_filter(filter1, filter2, op: op, context: result)
|
31
|
+
}
|
32
|
+
|
33
|
+
non_boolean_filter
|
34
|
+
: attribute_filter
|
35
|
+
| nested_filter
|
36
|
+
| grouped_filter
|
37
|
+
| not_filter
|
38
|
+
|
39
|
+
attribute_filter
|
40
|
+
: attr_path PR
|
41
|
+
{
|
42
|
+
attr, op = val
|
43
|
+
result = handler.on_attribute_filter(attr[:path], nil, op: op, schema: attr[:schema], context: result)
|
44
|
+
}
|
45
|
+
| attr_path comp_op comp_value
|
46
|
+
{
|
47
|
+
attr, op, v = val
|
48
|
+
result = handler.on_attribute_filter(attr[:path], v, op: op, schema: attr[:schema], context: result)
|
49
|
+
}
|
50
|
+
|
51
|
+
nested_filter
|
52
|
+
: attr_path LBRACKET filter RBRACKET
|
53
|
+
{
|
54
|
+
attr, _, filter, _ = val
|
55
|
+
result = handler.on_nested_filter(attr[:path], filter, schema: attr[:schema], context: result)
|
56
|
+
}
|
57
|
+
|
58
|
+
grouped_filter
|
59
|
+
: LPAREN filter RPAREN
|
60
|
+
{
|
61
|
+
result = val[1]
|
62
|
+
}
|
63
|
+
|
64
|
+
not_filter
|
65
|
+
: NOT grouped_filter
|
66
|
+
{
|
67
|
+
_, filter = val
|
68
|
+
result = handler.on_not_filter(filter, context: result)
|
69
|
+
}
|
70
|
+
|
71
|
+
attr_path
|
72
|
+
: SCHEMA attr_path_elements
|
73
|
+
{
|
74
|
+
schema, path = val
|
75
|
+
result = { schema: schema, path: path }
|
76
|
+
}
|
77
|
+
| attr_path_elements
|
78
|
+
{
|
79
|
+
result = { path: val.last }
|
80
|
+
}
|
81
|
+
|
82
|
+
attr_path_elements
|
83
|
+
: attr_path_elements DOT ATTRNAME
|
84
|
+
{
|
85
|
+
result = [*val.first, val.last]
|
86
|
+
}
|
87
|
+
| ATTRNAME
|
88
|
+
{
|
89
|
+
result = [val.last]
|
90
|
+
}
|
91
|
+
|
92
|
+
comp_op
|
93
|
+
: EQ
|
94
|
+
| NE
|
95
|
+
| GT
|
96
|
+
| GE
|
97
|
+
| LT
|
98
|
+
| LE
|
99
|
+
| CO
|
100
|
+
| SW
|
101
|
+
| EW
|
102
|
+
|
103
|
+
comp_value
|
104
|
+
: BOOLEAN
|
105
|
+
| NULL
|
106
|
+
| NUMBER
|
107
|
+
| STRING
|
108
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'parser.tab'
|
2
|
+
|
3
|
+
module Scim2
|
4
|
+
module Filter
|
5
|
+
##
|
6
|
+
# Implements a SCIM2 compliant event-based parser for query filters. The parser
|
7
|
+
# will emit four different events to a handler implemention as it encounters
|
8
|
+
# various components within the filter. For reference, see:
|
9
|
+
#
|
10
|
+
# * {SimpleHandler#on_not_filter}
|
11
|
+
# * {SimpleHandler#on_attribute_filter}
|
12
|
+
# * {SimpleHandler#on_logical_filter}
|
13
|
+
# * {SimpleHandler#on_nested_filter}
|
14
|
+
##
|
15
|
+
class Parser < ::Racc::Parser
|
16
|
+
|
17
|
+
attr_reader :handler
|
18
|
+
|
19
|
+
# @param handler <Handler> a handler object that responds to all four events
|
20
|
+
def initialize(handler = SimpleHandler.new)
|
21
|
+
super()
|
22
|
+
@handler = handler
|
23
|
+
end
|
24
|
+
|
25
|
+
# Required by {::Racc::Parser} to emit the next token to the parser. This
|
26
|
+
# method should generally not be called directly.
|
27
|
+
# @return <String> the next token
|
28
|
+
def next_token
|
29
|
+
@lexer.next_token
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parses a given string input
|
33
|
+
# @param string <String> the filter to parse
|
34
|
+
# @return <Object> returns the last object emitted by the handler
|
35
|
+
def parse(string)
|
36
|
+
@lexer = Lexer.new
|
37
|
+
@lexer.scan_setup(string)
|
38
|
+
do_parse
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,327 @@
|
|
1
|
+
#
|
2
|
+
# DO NOT MODIFY!!!!
|
3
|
+
# This file is automatically generated by Racc 1.5.2
|
4
|
+
# from Racc grammar file "".
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'racc/parser.rb'
|
8
|
+
module Scim2
|
9
|
+
module Filter
|
10
|
+
class Parser < Racc::Parser
|
11
|
+
##### State transition tables begin ###
|
12
|
+
|
13
|
+
racc_action_table = [
|
14
|
+
20, 21, 22, 23, 24, 25, 26, 27, 28, 17,
|
15
|
+
14, 10, 9, 16, 9, 19, 10, 9, 10, 9,
|
16
|
+
13, 11, 13, 15, 32, 13, 11, 13, 11, 10,
|
17
|
+
9, 10, 9, 38, 37, 39, 40, 15, 13, 11,
|
18
|
+
13, 11, 44, 15, 33, 32, 42, 43, 16 ]
|
19
|
+
|
20
|
+
racc_action_check = [
|
21
|
+
8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
|
22
|
+
1, 0, 0, 2, 10, 8, 9, 9, 15, 15,
|
23
|
+
0, 0, 11, 1, 12, 9, 9, 15, 15, 16,
|
24
|
+
16, 19, 19, 18, 18, 18, 18, 41, 16, 16,
|
25
|
+
19, 19, 41, 29, 14, 31, 29, 32, 34 ]
|
26
|
+
|
27
|
+
racc_action_pointer = [
|
28
|
+
-3, 10, 1, nil, nil, nil, nil, nil, -2, 2,
|
29
|
+
-1, -1, -1, nil, 44, 4, 15, nil, 14, 17,
|
30
|
+
nil, nil, nil, nil, nil, nil, nil, nil, nil, 30,
|
31
|
+
nil, 20, 24, nil, 36, nil, nil, nil, nil, nil,
|
32
|
+
nil, 24, nil, nil, nil ]
|
33
|
+
|
34
|
+
racc_action_default = [
|
35
|
+
-31, -31, -1, -3, -5, -6, -7, -8, -31, -31,
|
36
|
+
-31, -31, -15, -17, -31, -31, -31, -9, -31, -31,
|
37
|
+
-18, -19, -20, -21, -22, -23, -24, -25, -26, -31,
|
38
|
+
-13, -14, -31, 45, -2, -4, -10, -27, -28, -29,
|
39
|
+
-30, -31, -12, -16, -11 ]
|
40
|
+
|
41
|
+
racc_goto_table = [
|
42
|
+
1, 34, 35, 30, 18, 36, 31, nil, nil, 29,
|
43
|
+
nil, nil, nil, nil, nil, nil, nil, nil, nil, 41 ]
|
44
|
+
|
45
|
+
racc_goto_check = [
|
46
|
+
1, 2, 3, 6, 9, 10, 11, nil, nil, 1,
|
47
|
+
nil, nil, nil, nil, nil, nil, nil, nil, nil, 1 ]
|
48
|
+
|
49
|
+
racc_goto_pointer = [
|
50
|
+
nil, 0, -14, -14, nil, nil, -7, nil, nil, -4,
|
51
|
+
-13, -5 ]
|
52
|
+
|
53
|
+
racc_goto_default = [
|
54
|
+
nil, nil, 2, 3, 4, 5, 6, 7, 8, nil,
|
55
|
+
nil, 12 ]
|
56
|
+
|
57
|
+
racc_reduce_table = [
|
58
|
+
0, 0, :racc_error,
|
59
|
+
1, 27, :_reduce_none,
|
60
|
+
3, 27, :_reduce_2,
|
61
|
+
1, 28, :_reduce_none,
|
62
|
+
3, 28, :_reduce_4,
|
63
|
+
1, 29, :_reduce_none,
|
64
|
+
1, 29, :_reduce_none,
|
65
|
+
1, 29, :_reduce_none,
|
66
|
+
1, 29, :_reduce_none,
|
67
|
+
2, 30, :_reduce_9,
|
68
|
+
3, 30, :_reduce_10,
|
69
|
+
4, 31, :_reduce_11,
|
70
|
+
3, 32, :_reduce_12,
|
71
|
+
2, 33, :_reduce_13,
|
72
|
+
2, 34, :_reduce_14,
|
73
|
+
1, 34, :_reduce_15,
|
74
|
+
3, 37, :_reduce_16,
|
75
|
+
1, 37, :_reduce_17,
|
76
|
+
1, 35, :_reduce_none,
|
77
|
+
1, 35, :_reduce_none,
|
78
|
+
1, 35, :_reduce_none,
|
79
|
+
1, 35, :_reduce_none,
|
80
|
+
1, 35, :_reduce_none,
|
81
|
+
1, 35, :_reduce_none,
|
82
|
+
1, 35, :_reduce_none,
|
83
|
+
1, 35, :_reduce_none,
|
84
|
+
1, 35, :_reduce_none,
|
85
|
+
1, 36, :_reduce_none,
|
86
|
+
1, 36, :_reduce_none,
|
87
|
+
1, 36, :_reduce_none,
|
88
|
+
1, 36, :_reduce_none ]
|
89
|
+
|
90
|
+
racc_reduce_n = 31
|
91
|
+
|
92
|
+
racc_shift_n = 45
|
93
|
+
|
94
|
+
racc_token_table = {
|
95
|
+
false => 0,
|
96
|
+
:error => 1,
|
97
|
+
:EQ => 2,
|
98
|
+
:NE => 3,
|
99
|
+
:GT => 4,
|
100
|
+
:GE => 5,
|
101
|
+
:LT => 6,
|
102
|
+
:LE => 7,
|
103
|
+
:CO => 8,
|
104
|
+
:SW => 9,
|
105
|
+
:EW => 10,
|
106
|
+
:PR => 11,
|
107
|
+
:AND => 12,
|
108
|
+
:OR => 13,
|
109
|
+
:NOT => 14,
|
110
|
+
:LPAREN => 15,
|
111
|
+
:RPAREN => 16,
|
112
|
+
:LBRACKET => 17,
|
113
|
+
:RBRACKET => 18,
|
114
|
+
:NULL => 19,
|
115
|
+
:BOOLEAN => 20,
|
116
|
+
:NUMBER => 21,
|
117
|
+
:STRING => 22,
|
118
|
+
:ATTRNAME => 23,
|
119
|
+
:SCHEMA => 24,
|
120
|
+
:DOT => 25 }
|
121
|
+
|
122
|
+
racc_nt_base = 26
|
123
|
+
|
124
|
+
racc_use_result_var = true
|
125
|
+
|
126
|
+
Racc_arg = [
|
127
|
+
racc_action_table,
|
128
|
+
racc_action_check,
|
129
|
+
racc_action_default,
|
130
|
+
racc_action_pointer,
|
131
|
+
racc_goto_table,
|
132
|
+
racc_goto_check,
|
133
|
+
racc_goto_default,
|
134
|
+
racc_goto_pointer,
|
135
|
+
racc_nt_base,
|
136
|
+
racc_reduce_table,
|
137
|
+
racc_token_table,
|
138
|
+
racc_shift_n,
|
139
|
+
racc_reduce_n,
|
140
|
+
racc_use_result_var ]
|
141
|
+
|
142
|
+
Racc_token_to_s_table = [
|
143
|
+
"$end",
|
144
|
+
"error",
|
145
|
+
"EQ",
|
146
|
+
"NE",
|
147
|
+
"GT",
|
148
|
+
"GE",
|
149
|
+
"LT",
|
150
|
+
"LE",
|
151
|
+
"CO",
|
152
|
+
"SW",
|
153
|
+
"EW",
|
154
|
+
"PR",
|
155
|
+
"AND",
|
156
|
+
"OR",
|
157
|
+
"NOT",
|
158
|
+
"LPAREN",
|
159
|
+
"RPAREN",
|
160
|
+
"LBRACKET",
|
161
|
+
"RBRACKET",
|
162
|
+
"NULL",
|
163
|
+
"BOOLEAN",
|
164
|
+
"NUMBER",
|
165
|
+
"STRING",
|
166
|
+
"ATTRNAME",
|
167
|
+
"SCHEMA",
|
168
|
+
"DOT",
|
169
|
+
"$start",
|
170
|
+
"filter",
|
171
|
+
"non_or_filter",
|
172
|
+
"non_boolean_filter",
|
173
|
+
"attribute_filter",
|
174
|
+
"nested_filter",
|
175
|
+
"grouped_filter",
|
176
|
+
"not_filter",
|
177
|
+
"attr_path",
|
178
|
+
"comp_op",
|
179
|
+
"comp_value",
|
180
|
+
"attr_path_elements" ]
|
181
|
+
|
182
|
+
Racc_debug_parser = false
|
183
|
+
|
184
|
+
##### State transition tables end #####
|
185
|
+
|
186
|
+
# reduce 0 omitted
|
187
|
+
|
188
|
+
# reduce 1 omitted
|
189
|
+
|
190
|
+
module_eval(<<'.,.,', 'parser.racc', 20)
|
191
|
+
def _reduce_2(val, _values, result)
|
192
|
+
filter1, op, filter2 = val
|
193
|
+
result = handler.on_logical_filter(filter1, filter2, op: op, context: result)
|
194
|
+
|
195
|
+
result
|
196
|
+
end
|
197
|
+
.,.,
|
198
|
+
|
199
|
+
# reduce 3 omitted
|
200
|
+
|
201
|
+
module_eval(<<'.,.,', 'parser.racc', 28)
|
202
|
+
def _reduce_4(val, _values, result)
|
203
|
+
filter1, op, filter2 = val
|
204
|
+
result = handler.on_logical_filter(filter1, filter2, op: op, context: result)
|
205
|
+
|
206
|
+
result
|
207
|
+
end
|
208
|
+
.,.,
|
209
|
+
|
210
|
+
# reduce 5 omitted
|
211
|
+
|
212
|
+
# reduce 6 omitted
|
213
|
+
|
214
|
+
# reduce 7 omitted
|
215
|
+
|
216
|
+
# reduce 8 omitted
|
217
|
+
|
218
|
+
module_eval(<<'.,.,', 'parser.racc', 41)
|
219
|
+
def _reduce_9(val, _values, result)
|
220
|
+
attr, op = val
|
221
|
+
result = handler.on_attribute_filter(attr[:path], nil, op: op, schema: attr[:schema], context: result)
|
222
|
+
|
223
|
+
result
|
224
|
+
end
|
225
|
+
.,.,
|
226
|
+
|
227
|
+
module_eval(<<'.,.,', 'parser.racc', 46)
|
228
|
+
def _reduce_10(val, _values, result)
|
229
|
+
attr, op, v = val
|
230
|
+
result = handler.on_attribute_filter(attr[:path], v, op: op, schema: attr[:schema], context: result)
|
231
|
+
|
232
|
+
result
|
233
|
+
end
|
234
|
+
.,.,
|
235
|
+
|
236
|
+
module_eval(<<'.,.,', 'parser.racc', 53)
|
237
|
+
def _reduce_11(val, _values, result)
|
238
|
+
attr, _, filter, _ = val
|
239
|
+
result = handler.on_nested_filter(attr[:path], filter, schema: attr[:schema], context: result)
|
240
|
+
|
241
|
+
result
|
242
|
+
end
|
243
|
+
.,.,
|
244
|
+
|
245
|
+
module_eval(<<'.,.,', 'parser.racc', 60)
|
246
|
+
def _reduce_12(val, _values, result)
|
247
|
+
result = val[1]
|
248
|
+
|
249
|
+
result
|
250
|
+
end
|
251
|
+
.,.,
|
252
|
+
|
253
|
+
module_eval(<<'.,.,', 'parser.racc', 66)
|
254
|
+
def _reduce_13(val, _values, result)
|
255
|
+
_, filter = val
|
256
|
+
result = handler.on_not_filter(filter, context: result)
|
257
|
+
|
258
|
+
result
|
259
|
+
end
|
260
|
+
.,.,
|
261
|
+
|
262
|
+
module_eval(<<'.,.,', 'parser.racc', 73)
|
263
|
+
def _reduce_14(val, _values, result)
|
264
|
+
schema, path = val
|
265
|
+
result = { schema: schema, path: path }
|
266
|
+
|
267
|
+
result
|
268
|
+
end
|
269
|
+
.,.,
|
270
|
+
|
271
|
+
module_eval(<<'.,.,', 'parser.racc', 78)
|
272
|
+
def _reduce_15(val, _values, result)
|
273
|
+
result = { path: val.last }
|
274
|
+
|
275
|
+
result
|
276
|
+
end
|
277
|
+
.,.,
|
278
|
+
|
279
|
+
module_eval(<<'.,.,', 'parser.racc', 84)
|
280
|
+
def _reduce_16(val, _values, result)
|
281
|
+
result = [*val.first, val.last]
|
282
|
+
|
283
|
+
result
|
284
|
+
end
|
285
|
+
.,.,
|
286
|
+
|
287
|
+
module_eval(<<'.,.,', 'parser.racc', 88)
|
288
|
+
def _reduce_17(val, _values, result)
|
289
|
+
result = [val.last]
|
290
|
+
|
291
|
+
result
|
292
|
+
end
|
293
|
+
.,.,
|
294
|
+
|
295
|
+
# reduce 18 omitted
|
296
|
+
|
297
|
+
# reduce 19 omitted
|
298
|
+
|
299
|
+
# reduce 20 omitted
|
300
|
+
|
301
|
+
# reduce 21 omitted
|
302
|
+
|
303
|
+
# reduce 22 omitted
|
304
|
+
|
305
|
+
# reduce 23 omitted
|
306
|
+
|
307
|
+
# reduce 24 omitted
|
308
|
+
|
309
|
+
# reduce 25 omitted
|
310
|
+
|
311
|
+
# reduce 26 omitted
|
312
|
+
|
313
|
+
# reduce 27 omitted
|
314
|
+
|
315
|
+
# reduce 28 omitted
|
316
|
+
|
317
|
+
# reduce 29 omitted
|
318
|
+
|
319
|
+
# reduce 30 omitted
|
320
|
+
|
321
|
+
def _reduce_none(val, _values, result)
|
322
|
+
val[0]
|
323
|
+
end
|
324
|
+
|
325
|
+
end # class Parser
|
326
|
+
end # module Filter
|
327
|
+
end # module Scim2
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Scim2
|
2
|
+
module Filter
|
3
|
+
##
|
4
|
+
# Reference implementation of parser handler which captures parsed filters into a deeply nested Hash structure.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# # userName sw "J"
|
8
|
+
#
|
9
|
+
# {
|
10
|
+
# sw: {
|
11
|
+
# path: [:userName],
|
12
|
+
# schema: nil,
|
13
|
+
# value: 'J',
|
14
|
+
# },
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # urn:ietf:params:scim:schemas:core:2.0:User:userType ne "Employee" and not (emails.value co "example.com" or emails.value co "example.org")
|
19
|
+
#
|
20
|
+
# {
|
21
|
+
# and: [
|
22
|
+
# {
|
23
|
+
# ne: {
|
24
|
+
# path: [:userType],
|
25
|
+
# schema: 'urn:ietf:params:scim:schemas:core:2.0:User',
|
26
|
+
# value: 'Employee',
|
27
|
+
# },
|
28
|
+
# },
|
29
|
+
# {
|
30
|
+
# not: {
|
31
|
+
# or: [
|
32
|
+
# {
|
33
|
+
# co: {
|
34
|
+
# path: %i[emails value],
|
35
|
+
# schema: nil,
|
36
|
+
# value: 'example.com',
|
37
|
+
# },
|
38
|
+
# },
|
39
|
+
# {
|
40
|
+
# co: {
|
41
|
+
# path: %i[emails value],
|
42
|
+
# schema: nil,
|
43
|
+
# value: 'example.org',
|
44
|
+
# },
|
45
|
+
# },
|
46
|
+
# ],
|
47
|
+
# },
|
48
|
+
# },
|
49
|
+
# ],
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# # emails[type eq "work"]
|
54
|
+
#
|
55
|
+
# {
|
56
|
+
# path: [:emails],
|
57
|
+
# schema: nil,
|
58
|
+
# nested: {
|
59
|
+
# eq: {
|
60
|
+
# path: [:type],
|
61
|
+
# schema: nil,
|
62
|
+
# value: 'work',
|
63
|
+
# },
|
64
|
+
# },
|
65
|
+
# }
|
66
|
+
#
|
67
|
+
# @note This handler is intended only as a reference implementation for custom
|
68
|
+
# handlers and is otherwise not designed for production use.
|
69
|
+
##
|
70
|
+
class SimpleHandler
|
71
|
+
|
72
|
+
# Handle NOT filters (e.g. `not (color eq "red")`)
|
73
|
+
# @param filter [Hash<Symbol, Object>] the internal filter being NOT'ed
|
74
|
+
# @return [Hash<Symbol, Object>]
|
75
|
+
def on_not_filter(filter, context:)
|
76
|
+
{ not: filter }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Handle basic attribute comparison filters (e.g. `preference.color eq "red"`)
|
80
|
+
# @param attribute_path [Array<Symbol>] the attribute name(s) being filtered on, split by `.`
|
81
|
+
# @param value [Object] the value being compared against
|
82
|
+
# @param op [Object] the comparison operator (e.g. `:eq`)
|
83
|
+
# @param schema [String] schema namespace of the attribute
|
84
|
+
# @return [Hash<Symbol, Object>]
|
85
|
+
def on_attribute_filter(attribute_path, value, context:, op:, schema: nil)
|
86
|
+
{ op => { path: attribute_path, value: value, schema: schema } }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Handle logical filters (e.g. `name.givenName sw "D" AND title co "VP"`)
|
90
|
+
# @param filter1 [Hash<Symbol, Object>] the left-hand side filter
|
91
|
+
# @param filter2 [Hash<Symbol, Object>] the right-hand side filter
|
92
|
+
# @param op [Object] the logical operator (e.g. `AND`)
|
93
|
+
# @return [Hash<Symbol, Object>]
|
94
|
+
def on_logical_filter(filter1, filter2, context:, op:)
|
95
|
+
{ op => [filter1, filter2] }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Handle nested filters (e.g. `emails[type eq "work"]`)
|
99
|
+
# @param attribute_path [Array<Symbol>] the attribute name(s) being filtered on, split by `.`
|
100
|
+
# @param filter [Hash<Symbol, Object>] the nested-filter inside the backets
|
101
|
+
# @param schema [String] schema namespace of the attribute
|
102
|
+
# @return [Hash<Symbol, Object>]
|
103
|
+
def on_nested_filter(attribute_path, filter, context:, schema: nil)
|
104
|
+
{ path: attribute_path, nested: filter, schema: schema }
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
metadata
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scim2-filter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David McCullars
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: racc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rexical
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.4'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.4'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: |
|
140
|
+
RFC7644 SCIM (System for Cross-domain Identity Management) 2.0 filter parser.
|
141
|
+
See https://tools.ietf.org/html/rfc7644#section-3.4.2.2
|
142
|
+
|
143
|
+
This gem implements a filter syntax parser as well as an optional integration
|
144
|
+
for integrating the filter with an Arel table.
|
145
|
+
email: david.mccullars@gmail.com
|
146
|
+
executables: []
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- README.md
|
151
|
+
- lib/scim2-filter.rb
|
152
|
+
- lib/scim2.rb
|
153
|
+
- lib/scim2/filter.rb
|
154
|
+
- lib/scim2/filter/arel_handler.rb
|
155
|
+
- lib/scim2/filter/lexer.rb
|
156
|
+
- lib/scim2/filter/lexer.rex
|
157
|
+
- lib/scim2/filter/lexer.rex.rb
|
158
|
+
- lib/scim2/filter/no_op_handler.rb
|
159
|
+
- lib/scim2/filter/parser.racc
|
160
|
+
- lib/scim2/filter/parser.rb
|
161
|
+
- lib/scim2/filter/parser.tab.rb
|
162
|
+
- lib/scim2/filter/simple_handler.rb
|
163
|
+
homepage: https://github.com/david-mccullars/scim2-filter
|
164
|
+
licenses:
|
165
|
+
- MIT
|
166
|
+
metadata: {}
|
167
|
+
post_install_message:
|
168
|
+
rdoc_options: []
|
169
|
+
require_paths:
|
170
|
+
- lib
|
171
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
requirements: []
|
182
|
+
rubygems_version: 3.2.3
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: Parser for SCIM query filters
|
186
|
+
test_files: []
|