cisco_node_utils 1.0.1 → 1.1.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/.gitignore +4 -0
- data/.rubocop.yml +81 -1
- data/.travis.yml +9 -0
- data/CHANGELOG.md +72 -6
- data/CONTRIBUTING.md +32 -7
- data/README.md +70 -7
- data/Rakefile +17 -0
- data/bin/check_metric_limits.rb +109 -0
- data/bin/git/hooks/commit-msg/enforce_style +81 -0
- data/bin/git/hooks/hook_lib +108 -0
- data/bin/git/hooks/hooks-wrapper +38 -0
- data/bin/git/hooks/post-flow-hotfix-start/update-version +24 -0
- data/bin/git/hooks/post-flow-release-finish/update-version +29 -0
- data/bin/git/hooks/post-flow-release-start/update-version +19 -0
- data/bin/git/hooks/post-merge/update-hooks +6 -0
- data/bin/git/hooks/post-rewrite/update-hooks +6 -0
- data/bin/git/hooks/pre-commit/rubocop +20 -0
- data/bin/git/hooks/pre-commit/validate-diffs +31 -0
- data/bin/git/hooks/pre-push/check-changelog +24 -0
- data/bin/git/hooks/pre-push/rubocop +7 -0
- data/bin/git/update-hooks +65 -0
- data/cisco_node_utils.gemspec +9 -3
- data/docs/README-develop-best-practices.md +404 -0
- data/docs/README-develop-node-utils-APIs.md +215 -365
- data/docs/README-maintainers.md +33 -3
- data/docs/template-router.rb +89 -91
- data/docs/template-test_router.rb +52 -55
- data/lib/.rubocop.yml +18 -0
- data/lib/cisco_node_utils.rb +2 -19
- data/lib/cisco_node_utils/README_YAML.md +1 -9
- data/lib/cisco_node_utils/bgp.rb +664 -0
- data/lib/cisco_node_utils/bgp_af.rb +530 -0
- data/lib/cisco_node_utils/bgp_neighbor.rb +425 -0
- data/lib/cisco_node_utils/bgp_neighbor_af.rb +709 -0
- data/lib/cisco_node_utils/cisco_cmn_utils.rb +59 -25
- data/lib/cisco_node_utils/command_reference.rb +72 -74
- data/lib/cisco_node_utils/command_reference_common.yaml +174 -9
- data/lib/cisco_node_utils/command_reference_common_bgp.yaml +535 -0
- data/lib/cisco_node_utils/command_reference_n7k.yaml +4 -0
- data/lib/cisco_node_utils/command_reference_n9k.yaml +0 -9
- data/lib/cisco_node_utils/configparser_lib.rb +152 -147
- data/lib/cisco_node_utils/dns_domain.rb +79 -0
- data/lib/cisco_node_utils/domain_name.rb +71 -0
- data/lib/cisco_node_utils/interface.rb +167 -161
- data/lib/cisco_node_utils/interface_ospf.rb +78 -81
- data/lib/cisco_node_utils/name_server.rb +64 -0
- data/lib/cisco_node_utils/node.rb +154 -198
- data/lib/cisco_node_utils/node_util.rb +61 -0
- data/lib/cisco_node_utils/ntp_config.rb +65 -0
- data/lib/cisco_node_utils/ntp_server.rb +76 -0
- data/lib/cisco_node_utils/platform.rb +174 -165
- data/lib/cisco_node_utils/radius_global.rb +146 -0
- data/lib/cisco_node_utils/radius_server.rb +295 -0
- data/lib/cisco_node_utils/router_ospf.rb +59 -63
- data/lib/cisco_node_utils/router_ospf_vrf.rb +226 -210
- data/lib/cisco_node_utils/snmpcommunity.rb +52 -58
- data/lib/cisco_node_utils/snmpgroup.rb +22 -23
- data/lib/cisco_node_utils/snmpserver.rb +99 -103
- data/lib/cisco_node_utils/snmpuser.rb +294 -274
- data/lib/cisco_node_utils/syslog_server.rb +92 -0
- data/lib/cisco_node_utils/syslog_settings.rb +69 -0
- data/lib/cisco_node_utils/tacacs_server.rb +137 -133
- data/lib/cisco_node_utils/tacacs_server_host.rb +84 -87
- data/lib/cisco_node_utils/version.rb +2 -1
- data/lib/cisco_node_utils/vlan.rb +28 -31
- data/lib/cisco_node_utils/vrf.rb +80 -0
- data/lib/cisco_node_utils/vtp.rb +100 -97
- data/lib/cisco_node_utils/yum.rb +15 -17
- data/tests/.rubocop.yml +15 -0
- data/tests/basetest.rb +81 -36
- data/tests/ciscotest.rb +38 -78
- data/{lib/cisco_node_utils → tests}/platform_info.rb +12 -8
- data/{lib/cisco_node_utils → tests}/platform_info.yaml +1 -1
- data/tests/test_bgp_af.rb +920 -0
- data/tests/test_bgp_neighbor.rb +403 -0
- data/tests/test_bgp_neighbor_af.rb +589 -0
- data/tests/test_command_config.rb +65 -62
- data/tests/test_command_reference.rb +31 -45
- data/tests/test_dns_domain.rb +113 -0
- data/tests/test_domain_name.rb +86 -0
- data/tests/test_interface.rb +424 -548
- data/tests/test_interface_ospf.rb +248 -432
- data/tests/test_interface_svi.rb +56 -79
- data/tests/test_interface_switchport.rb +196 -272
- data/tests/test_name_server.rb +85 -0
- data/tests/test_node.rb +7 -6
- data/tests/test_node_ext.rb +133 -186
- data/tests/test_ntp_config.rb +49 -0
- data/tests/test_ntp_server.rb +74 -0
- data/tests/test_platform.rb +58 -37
- data/tests/test_radius_global.rb +78 -0
- data/tests/test_radius_server.rb +185 -0
- data/tests/test_router_bgp.rb +838 -0
- data/tests/test_router_ospf.rb +49 -80
- data/tests/test_router_ospf_vrf.rb +274 -392
- data/tests/test_snmpcommunity.rb +128 -172
- data/tests/test_snmpgroup.rb +12 -14
- data/tests/test_snmpserver.rb +160 -189
- data/tests/test_snmpuser.rb +568 -717
- data/tests/test_syslog_server.rb +88 -0
- data/tests/test_syslog_settings.rb +54 -0
- data/tests/test_tacacs_server.rb +113 -148
- data/tests/test_tacacs_server_host.rb +108 -161
- data/tests/test_vlan.rb +63 -79
- data/tests/test_vrf.rb +92 -0
- data/tests/test_vtp.rb +108 -126
- data/tests/test_yum.rb +47 -41
- metadata +92 -56
- data/.rubocop_todo.yml +0 -293
- data/docs/.rubocop.yml +0 -13
- data/docs/template-feature.rb +0 -45
- data/docs/template-test_feature.rb +0 -51
- data/tests/test_all_cisco.rb +0 -46
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
#
|
|
2
|
-
# NXAPI implementation of SnmpCommunity class
|
|
3
|
-
#
|
|
4
1
|
# December 2014, Alex Hunsberger
|
|
5
2
|
#
|
|
6
3
|
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
|
@@ -17,75 +14,72 @@
|
|
|
17
14
|
# See the License for the specific language governing permissions and
|
|
18
15
|
# limitations under the License.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
require_relative 'node_util'
|
|
21
18
|
|
|
22
19
|
module Cisco
|
|
23
|
-
class
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def initialize(name, group, instantiate=true)
|
|
28
|
-
raise TypeError unless name.is_a?(String) and group.is_a?(String)
|
|
29
|
-
@name = name
|
|
20
|
+
# SnmpCommunity - node utility class for SNMP community config management
|
|
21
|
+
class SnmpCommunity < NodeUtil
|
|
22
|
+
@communities = nil
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
def initialize(name, group, instantiate=true)
|
|
25
|
+
fail TypeError unless name.is_a?(String) && group.is_a?(String)
|
|
26
|
+
@name = name
|
|
27
|
+
return unless instantiate
|
|
28
|
+
config_set('snmp_community', 'community', '', name, group)
|
|
33
29
|
end
|
|
34
|
-
end
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
def self.communities
|
|
32
|
+
@communities = {}
|
|
33
|
+
comms = config_get('snmp_community', 'all_communities')
|
|
34
|
+
unless comms.nil?
|
|
35
|
+
comms.each do |comm|
|
|
36
|
+
@communities[comm] = SnmpCommunity.new(comm, '', false)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
@communities
|
|
43
40
|
end
|
|
44
|
-
@@communities
|
|
45
|
-
end
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
42
|
+
def destroy
|
|
43
|
+
# CLI requires specifying a group even for "no" commands
|
|
44
|
+
config_set('snmp_community', 'community', 'no', @name, 'null')
|
|
45
|
+
end
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
# name is read only
|
|
48
|
+
# def name
|
|
49
|
+
# @name
|
|
50
|
+
# end
|
|
57
51
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
def group
|
|
53
|
+
result = config_get('snmp_community', 'group', @name)
|
|
54
|
+
result.nil? ? SnmpCommunity.default_group : result.first
|
|
55
|
+
end
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
def group=(group)
|
|
58
|
+
fail TypeError unless group.is_a?(String)
|
|
59
|
+
config_set('snmp_community', 'group', @name, group)
|
|
60
|
+
end
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
def self.default_group
|
|
63
|
+
config_get_default('snmp_community', 'group')
|
|
64
|
+
end
|
|
71
65
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
def acl
|
|
67
|
+
result = config_get('snmp_community', 'acl', @name)
|
|
68
|
+
result.nil? ? SnmpCommunity.default_acl : result.first
|
|
69
|
+
end
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
def acl=(acl)
|
|
72
|
+
fail TypeError unless acl.is_a?(String)
|
|
73
|
+
if acl.empty?
|
|
74
|
+
acl = self.acl
|
|
75
|
+
config_set('snmp_community', 'acl', 'no', @name, acl) unless acl.empty?
|
|
76
|
+
else
|
|
77
|
+
config_set('snmp_community', 'acl', '', @name, acl)
|
|
78
|
+
end
|
|
84
79
|
end
|
|
85
|
-
end
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
def self.default_acl
|
|
82
|
+
config_get_default('snmp_community', 'acl')
|
|
83
|
+
end
|
|
89
84
|
end
|
|
90
85
|
end
|
|
91
|
-
end
|
|
@@ -21,35 +21,34 @@
|
|
|
21
21
|
# purpose of group; thus this provider utility does not create snmp groups
|
|
22
22
|
# and is limited to reporting group (role) existence only.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
require_relative 'node_util'
|
|
25
25
|
|
|
26
26
|
module Cisco
|
|
27
|
-
class
|
|
28
|
-
|
|
27
|
+
# SnmpGroup - node utility class for SNMP group configuration management
|
|
28
|
+
class SnmpGroup < NodeUtil
|
|
29
|
+
attr_reader :name
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@name = name
|
|
35
|
-
end
|
|
31
|
+
def initialize(name)
|
|
32
|
+
fail TypeError unless name.is_a?(String)
|
|
33
|
+
@name = name
|
|
34
|
+
end
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
def self.groups
|
|
37
|
+
group_ids = config_get('snmp_group', 'group')
|
|
38
|
+
return {} if group_ids.nil?
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
hash = {}
|
|
41
|
+
group_ids.each do |name|
|
|
42
|
+
hash[name] = SnmpGroup.new(name)
|
|
43
|
+
end
|
|
44
|
+
hash
|
|
44
45
|
end
|
|
45
|
-
hash
|
|
46
|
-
end
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
def self.exists?(group)
|
|
48
|
+
fail ArgumentError if group.empty?
|
|
49
|
+
fail TypeError unless group.is_a? String
|
|
50
|
+
groups = config_get('snmp_group', 'group')
|
|
51
|
+
(!groups.nil? && groups.include?(group))
|
|
52
|
+
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
-
end
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
#
|
|
2
|
-
# NXAPI implementation of SnmpCommunity class
|
|
3
|
-
#
|
|
4
1
|
# November 2014, Alex Hunsberger
|
|
5
2
|
#
|
|
6
3
|
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
|
@@ -17,134 +14,133 @@
|
|
|
17
14
|
# See the License for the specific language governing permissions and
|
|
18
15
|
# limitations under the License.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
require_relative 'node_util'
|
|
21
18
|
|
|
22
19
|
module Cisco
|
|
23
|
-
class
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
20
|
+
# SnmpServer - node utility class for SNMP server management
|
|
21
|
+
class SnmpServer < NodeUtil
|
|
22
|
+
def aaa_user_cache_timeout
|
|
23
|
+
match = config_get('snmp_server', 'aaa_user_cache_timeout')
|
|
24
|
+
# regex in yaml returns an array result, use .first to get match
|
|
25
|
+
match.nil? ? default_aaa_user_cache_timeout : match.first.to_i
|
|
26
|
+
end
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
def aaa_user_cache_timeout=(timeout)
|
|
29
|
+
if timeout == default_aaa_user_cache_timeout
|
|
30
|
+
config_set('snmp_server', 'aaa_user_cache_timeout', 'no',
|
|
31
|
+
aaa_user_cache_timeout)
|
|
32
|
+
else
|
|
33
|
+
config_set('snmp_server', 'aaa_user_cache_timeout', '', timeout)
|
|
34
|
+
end
|
|
38
35
|
end
|
|
39
|
-
end
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
def default_aaa_user_cache_timeout
|
|
38
|
+
config_get_default('snmp_server', 'aaa_user_cache_timeout')
|
|
39
|
+
end
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
def location
|
|
42
|
+
match = config_get('snmp_server', 'location')
|
|
43
|
+
match.nil? ? default_location : match
|
|
44
|
+
end
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
def location=(location)
|
|
47
|
+
fail TypeError unless location.is_a?(String)
|
|
48
|
+
if location.empty?
|
|
49
|
+
config_set('snmp_server', 'location', 'no', '')
|
|
50
|
+
else
|
|
51
|
+
config_set('snmp_server', 'location', '', location)
|
|
52
|
+
end
|
|
56
53
|
end
|
|
57
|
-
end
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
def default_location
|
|
56
|
+
config_get_default('snmp_server', 'location')
|
|
57
|
+
end
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
def contact
|
|
60
|
+
match = config_get('snmp_server', 'contact')
|
|
61
|
+
match.nil? ? default_contact : match
|
|
62
|
+
end
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
def contact=(contact)
|
|
65
|
+
fail TypeError unless contact.is_a?(String)
|
|
66
|
+
if contact.empty?
|
|
67
|
+
config_set('snmp_server', 'contact', 'no', '')
|
|
68
|
+
else
|
|
69
|
+
config_set('snmp_server', 'contact', '', contact)
|
|
70
|
+
end
|
|
74
71
|
end
|
|
75
|
-
end
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
def default_contact
|
|
74
|
+
config_get_default('snmp_server', 'contact')
|
|
75
|
+
end
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
def packet_size
|
|
78
|
+
match = config_get('snmp_server', 'packet_size')
|
|
79
|
+
# regex in yaml returns an array result, use .first to get match
|
|
80
|
+
match.nil? ? default_packet_size : match.first.to_i
|
|
81
|
+
end
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
def packet_size=(size)
|
|
84
|
+
if size == 0
|
|
85
|
+
ps = packet_size
|
|
86
|
+
config_set('snmp_server', 'packet_size', 'no', ps) unless ps == 0
|
|
87
|
+
else
|
|
88
|
+
config_set('snmp_server', 'packet_size', '', size)
|
|
89
|
+
end
|
|
93
90
|
end
|
|
94
|
-
end
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
def default_packet_size
|
|
93
|
+
config_get_default('snmp_server', 'packet_size')
|
|
94
|
+
end
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
def global_enforce_priv?
|
|
97
|
+
!config_get('snmp_server', 'global_enforce_priv').nil?
|
|
98
|
+
end
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
def global_enforce_priv=(enforce)
|
|
101
|
+
if enforce
|
|
102
|
+
config_set('snmp_server', 'global_enforce_priv', '')
|
|
103
|
+
else
|
|
104
|
+
config_set('snmp_server', 'global_enforce_priv', 'no')
|
|
105
|
+
end
|
|
109
106
|
end
|
|
110
|
-
end
|
|
111
107
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
def default_global_enforce_priv
|
|
109
|
+
config_get_default('snmp_server', 'global_enforce_priv')
|
|
110
|
+
end
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
def protocol?
|
|
113
|
+
match = config_get('snmp_server', 'protocol')
|
|
114
|
+
!match.nil? && match.include?('Enable')
|
|
115
|
+
end
|
|
120
116
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
def protocol=(enable)
|
|
118
|
+
if enable
|
|
119
|
+
config_set('snmp_server', 'protocol', '')
|
|
120
|
+
else
|
|
121
|
+
config_set('snmp_server', 'protocol', 'no')
|
|
122
|
+
end
|
|
126
123
|
end
|
|
127
|
-
end
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
def default_protocol
|
|
126
|
+
config_get_default('snmp_server', 'protocol')
|
|
127
|
+
end
|
|
132
128
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
129
|
+
def tcp_session_auth?
|
|
130
|
+
match = config_get('snmp_server', 'tcp_session_auth')
|
|
131
|
+
!match.nil? && match.include?('Enabled')
|
|
132
|
+
end
|
|
137
133
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
def tcp_session_auth=(enable)
|
|
135
|
+
if enable
|
|
136
|
+
config_set('snmp_server', 'tcp_session_auth', '', 'auth')
|
|
137
|
+
else
|
|
138
|
+
config_set('snmp_server', 'tcp_session_auth', 'no', '')
|
|
139
|
+
end
|
|
143
140
|
end
|
|
144
|
-
end
|
|
145
141
|
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
def default_tcp_session_auth
|
|
143
|
+
config_get_default('snmp_server', 'tcp_session_auth')
|
|
144
|
+
end
|
|
148
145
|
end
|
|
149
146
|
end
|
|
150
|
-
end
|
|
@@ -12,331 +12,351 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
require_relative 'node_util'
|
|
16
16
|
|
|
17
17
|
module Cisco
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@privproto = privproto
|
|
52
|
-
@groups_arr = groups
|
|
53
|
-
|
|
54
|
-
authprotostr = _auth_sym_to_str(authproto)
|
|
55
|
-
privprotostr = _priv_sym_to_str(privproto)
|
|
56
|
-
|
|
57
|
-
# Config string syntax:
|
|
58
|
-
# [no] snmp-server user <user> [group] [auth {md5|sha} <passwd1> [priv [aes-128] <passwd2>] [localizedkey] [engineID <id>]]
|
|
59
|
-
if instantiate
|
|
60
|
-
# assume if multiple groups, apply all config to each
|
|
61
|
-
groups = [""] if groups.empty?
|
|
62
|
-
groups.each { |group|
|
|
63
|
-
@@node.config_set("snmp_user", "user", "",
|
|
64
|
-
name,
|
|
65
|
-
group,
|
|
66
|
-
authpass.empty? ? "" : "auth #{authprotostr} #{authpass}",
|
|
67
|
-
privpass.empty? ? "" : "priv #{privprotostr} #{privpass}",
|
|
68
|
-
localizedkey ? "localizedkey" : "",
|
|
69
|
-
engineid.empty? ? "" : "engineID #{engineid}")
|
|
70
|
-
}
|
|
18
|
+
# SnmpUser - node utility class for SNMP user configuration management
|
|
19
|
+
class SnmpUser < NodeUtil
|
|
20
|
+
def initialize(name, groups, authproto, authpass, privproto,
|
|
21
|
+
privpass, localizedkey, engineid, instantiate=true)
|
|
22
|
+
initialize_validator(name, groups, authproto, authpass, privproto,
|
|
23
|
+
privpass, engineid, instantiate)
|
|
24
|
+
@name = name
|
|
25
|
+
@engine_id = engineid
|
|
26
|
+
|
|
27
|
+
@authproto = authproto
|
|
28
|
+
@privproto = privproto
|
|
29
|
+
@groups_arr = groups
|
|
30
|
+
|
|
31
|
+
authprotostr = _auth_sym_to_str(authproto)
|
|
32
|
+
privprotostr = _priv_sym_to_str(privproto)
|
|
33
|
+
|
|
34
|
+
return unless instantiate
|
|
35
|
+
# Config string syntax:
|
|
36
|
+
# [no] snmp-server user <user> [group] ...
|
|
37
|
+
# [auth {md5|sha} <passwd1>
|
|
38
|
+
# [priv [aes-128] <passwd2>] [localizedkey] [engineID <id>]
|
|
39
|
+
# ]
|
|
40
|
+
# Assume if multiple groups, apply all config to each
|
|
41
|
+
groups = [''] if groups.empty?
|
|
42
|
+
groups.each do |group|
|
|
43
|
+
config_set('snmp_user', 'user', '',
|
|
44
|
+
name,
|
|
45
|
+
group,
|
|
46
|
+
authpass.empty? ? '' : "auth #{authprotostr} #{authpass}",
|
|
47
|
+
privpass.empty? ? '' : "priv #{privprotostr} #{privpass}",
|
|
48
|
+
localizedkey ? 'localizedkey' : '',
|
|
49
|
+
engineid.empty? ? '' : "engineID #{engineid}")
|
|
50
|
+
end
|
|
71
51
|
end
|
|
72
|
-
end
|
|
73
52
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
def initialize_validator(name, groups, authproto, authpass, privproto,
|
|
54
|
+
privpass, engineid, instantiate)
|
|
55
|
+
fail TypeError unless name.is_a?(String) &&
|
|
56
|
+
groups.is_a?(Array) &&
|
|
57
|
+
authproto.is_a?(Symbol) &&
|
|
58
|
+
authpass.is_a?(String) &&
|
|
59
|
+
privproto.is_a?(Symbol) &&
|
|
60
|
+
privpass.is_a?(String) &&
|
|
61
|
+
engineid.is_a?(String)
|
|
62
|
+
fail ArgumentError if name.empty?
|
|
63
|
+
# empty password but protocol provided = bad
|
|
64
|
+
# non-empty password and no protocol provided = bad
|
|
65
|
+
if authpass.empty?
|
|
66
|
+
fail ArgumentError if [:sha, :md5].include?(authproto) && instantiate
|
|
67
|
+
else
|
|
68
|
+
fail ArgumentError unless [:sha, :md5].include?(authproto)
|
|
69
|
+
end
|
|
70
|
+
if privpass.empty?
|
|
71
|
+
fail ArgumentError if [:des, :aes128].include?(privproto) && instantiate
|
|
72
|
+
else
|
|
73
|
+
fail ArgumentError unless [:des, :aes128].include?(privproto)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
ENGINE_ID_PATTERN = /([0-9]{1,3}(:[0-9]{1,3}){4,31})/
|
|
78
|
+
def self.users
|
|
79
|
+
users_hash = {}
|
|
80
|
+
# config_get returns hash if 1 user, array if multiple, nil if none
|
|
81
|
+
users = config_get('snmp_user', 'user')
|
|
82
|
+
return users_hash if users.nil?
|
|
79
83
|
users = [users] if users.is_a?(Hash)
|
|
80
|
-
users.each
|
|
81
|
-
name = user[
|
|
82
|
-
engineid = user[
|
|
84
|
+
users.each do |user|
|
|
85
|
+
name = user['user']
|
|
86
|
+
engineid = user['engineID']
|
|
83
87
|
if engineid.nil?
|
|
84
|
-
|
|
88
|
+
index = name
|
|
85
89
|
else
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
engineid_str = engineid.match(ENGINE_ID_PATTERN)[1]
|
|
91
|
+
index = name + ' ' + engineid_str
|
|
88
92
|
end
|
|
89
|
-
auth = _auth_str_to_sym(user[
|
|
90
|
-
priv = _priv_str_to_sym(user[
|
|
93
|
+
auth = _auth_str_to_sym(user['auth'])
|
|
94
|
+
priv = _priv_str_to_sym(user['priv'])
|
|
91
95
|
|
|
92
96
|
groups_arr = []
|
|
93
97
|
groups = _user_to_groups(user)
|
|
94
|
-
groups.each { |group| groups_arr << group[
|
|
98
|
+
groups.each { |group| groups_arr << group['group'].strip }
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
users_hash[index] = SnmpUser.new(name, groups_arr, auth,
|
|
101
|
+
'', priv, '', false,
|
|
102
|
+
engineid.nil? ? '' : engineid_str,
|
|
103
|
+
false)
|
|
104
|
+
end
|
|
105
|
+
users_hash
|
|
99
106
|
end
|
|
100
|
-
@@users
|
|
101
|
-
end
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
108
|
+
def destroy
|
|
109
|
+
# The parser doesn't care what the real value is but need to come to the
|
|
110
|
+
# end of the parser chain. Hence we just pass in some fake values for
|
|
111
|
+
# auth method and password
|
|
112
|
+
unless auth_password.nil? || auth_password.empty?
|
|
113
|
+
auth_str = "auth #{_auth_sym_to_str(auth_protocol)} #{auth_password}"
|
|
114
|
+
local_str = 'localizedkey'
|
|
115
|
+
end
|
|
116
|
+
unless priv_password.nil? || priv_password.empty?
|
|
117
|
+
priv_str = "priv #{_priv_sym_to_str(priv_protocol)} #{priv_password}"
|
|
118
|
+
end
|
|
119
|
+
config_set('snmp_user', 'user', 'no',
|
|
120
|
+
@name, '', auth_str, priv_str, local_str,
|
|
121
|
+
@engine_id.empty? ? '' : "engineID #{@engine_id}")
|
|
122
|
+
SnmpUser.users.delete(@name + ' ' + @engine_id)
|
|
123
|
+
end
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
attr_reader :name
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
def groups
|
|
128
|
+
@groups_arr
|
|
129
|
+
end
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
def self.default_groups
|
|
132
|
+
[config_get_default('snmp_user', 'group')]
|
|
133
|
+
end
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
def auth_protocol
|
|
136
|
+
@authproto
|
|
137
|
+
end
|
|
132
138
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
def self.default_auth_protocol
|
|
140
|
+
_auth_str_to_sym(config_get_default('snmp_user', 'auth_protocol'))
|
|
141
|
+
end
|
|
136
142
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
def self.default_auth_password
|
|
144
|
+
config_get_default('snmp_user', 'auth_password')
|
|
145
|
+
end
|
|
140
146
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
users =
|
|
147
|
+
def self.auth_password(name, engine_id)
|
|
148
|
+
if engine_id.empty?
|
|
149
|
+
users = config_get('snmp_user', 'auth_password')
|
|
144
150
|
return nil if users.nil?
|
|
145
|
-
users.each_entry { |user|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
else
|
|
149
|
-
users = @@node.config_get("snmp_user", "auth_password_with_engine_id")
|
|
151
|
+
users.each_entry { |user| return user[1] if user[0] == name }
|
|
152
|
+
else
|
|
153
|
+
users = config_get('snmp_user', 'auth_password_with_engine_id')
|
|
150
154
|
return nil if users.nil?
|
|
151
|
-
users.each_entry
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
users.each_entry do |user|
|
|
156
|
+
return user[1] if user[0] == name && user[2] == engine_id
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
nil
|
|
154
160
|
end
|
|
155
|
-
nil
|
|
156
|
-
end
|
|
157
161
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
def auth_password
|
|
163
|
+
SnmpUser.auth_password(@name, @engine_id)
|
|
164
|
+
end
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
def priv_protocol
|
|
167
|
+
@privproto
|
|
168
|
+
end
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
170
|
+
def self.priv_password(name, engine_id)
|
|
171
|
+
if engine_id.empty?
|
|
172
|
+
users = config_get('snmp_user', 'priv_password')
|
|
173
|
+
unless users.nil?
|
|
174
|
+
users.each_entry { |user| return user[1] if user[0] == name }
|
|
175
|
+
end
|
|
176
|
+
else
|
|
177
|
+
users = config_get('snmp_user', 'priv_password_with_engine_id')
|
|
178
|
+
unless users.nil?
|
|
179
|
+
users.each_entry do |user|
|
|
180
|
+
return user[1] if user[0] == name && user[2] == engine_id
|
|
181
|
+
end
|
|
182
|
+
end
|
|
180
183
|
end
|
|
184
|
+
nil
|
|
181
185
|
end
|
|
182
|
-
nil
|
|
183
|
-
end
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
def priv_password
|
|
188
|
+
SnmpUser.priv_password(@name, @engine_id)
|
|
189
|
+
end
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
def self.default_priv_protocol
|
|
192
|
+
_priv_str_to_sym(config_get_default('snmp_user', 'priv_protocol'))
|
|
193
|
+
end
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
def self.default_priv_password
|
|
196
|
+
config_get_default('snmp_user', 'priv_password')
|
|
197
|
+
end
|
|
196
198
|
|
|
197
|
-
|
|
199
|
+
attr_reader :engine_id
|
|
198
200
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
def self.default_engine_id
|
|
202
|
+
config_get_default('snmp_user', 'engine_id')
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Passwords are hashed and so cannot be retrieved directly, but can be
|
|
206
|
+
# checked for equality. This is done by creating a fake user with the
|
|
207
|
+
# password and then comparing the hashes
|
|
208
|
+
def auth_password_equal?(input_pw, is_localized=false)
|
|
209
|
+
input_pw = input_pw.to_s unless input_pw.is_a?(String)
|
|
210
|
+
# If we provide no password, and no password present, it's a match!
|
|
211
|
+
return true if input_pw.empty? && auth_protocol == :none
|
|
212
|
+
# If we provide no password, but a password is present, or vice versa...
|
|
213
|
+
return false if input_pw.empty? || auth_protocol == :none
|
|
214
|
+
# OK, we have an input password, and a password is configured
|
|
215
|
+
current_pw = auth_password
|
|
216
|
+
if current_pw.nil?
|
|
217
|
+
fail "SNMP user #{@name} #{@engine_id} has auth #{auth_protocol} " \
|
|
218
|
+
"but no password?\n" + @@node.show('show run snmp all')
|
|
219
|
+
end
|
|
202
220
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
throw TypeError unless passwd.is_a?(String)
|
|
208
|
-
return false if passwd.empty? or _auth_sym_to_str(auth_protocol).empty?
|
|
209
|
-
dummypw = passwd
|
|
210
|
-
pw = nil
|
|
211
|
-
|
|
212
|
-
if is_localized
|
|
213
|
-
# In this case, the password is hashed. We only need to get current
|
|
214
|
-
# running config to compare
|
|
215
|
-
pw = auth_password
|
|
216
|
-
else
|
|
221
|
+
if is_localized
|
|
222
|
+
# In this case, the password is already hashed.
|
|
223
|
+
hashed_pw = input_pw
|
|
224
|
+
else
|
|
217
225
|
# In this case passed in password is clear text while the running
|
|
218
|
-
# config is hashed value. We need to hash the
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
226
|
+
# config is hashed value. We need to hash the passed in clear text.
|
|
227
|
+
|
|
228
|
+
# Create dummy user
|
|
229
|
+
config_set('snmp_user', 'user', '', 'dummy_user', '',
|
|
230
|
+
"auth #{_auth_sym_to_str(auth_protocol)} #{input_pw}",
|
|
231
|
+
'', '',
|
|
232
|
+
@engine_id.empty? ? '' : "engineID #{@engine_id}")
|
|
233
|
+
|
|
234
|
+
# Retrieve password hashes
|
|
235
|
+
hashed_pw = SnmpUser.auth_password('dummy_user', @engine_id)
|
|
236
|
+
if hashed_pw.nil?
|
|
237
|
+
fail "SNMP dummy user #{dummy_user} #{@engine_id} was configured " \
|
|
238
|
+
"but password is missing?\n" + @@node.show('show run snmp all')
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Delete dummy user
|
|
242
|
+
config_set('snmp_user', 'user', 'no', 'dummy_user', '',
|
|
243
|
+
"auth #{_auth_sym_to_str(auth_protocol)} #{hashed_pw}",
|
|
244
|
+
'', 'localizedkey',
|
|
245
|
+
@engine_id.empty? ? '' : "engineID #{@engine_id}")
|
|
246
|
+
end
|
|
247
|
+
hashed_pw == current_pw
|
|
236
248
|
end
|
|
237
|
-
return false if pw.nil? or dummypw.nil?
|
|
238
|
-
pw == dummypw
|
|
239
|
-
end
|
|
240
249
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
# Passwords are hashed and so cannot be retrieved directly, but can be
|
|
251
|
+
# checked for equality. This is done by creating a fake user with the
|
|
252
|
+
# password and then comparing the hashes
|
|
253
|
+
def priv_password_equal?(input_pw, is_localized=false)
|
|
254
|
+
input_pw = input_pw.to_s unless input_pw.is_a?(String)
|
|
255
|
+
# If no input password, and no password present, true!
|
|
256
|
+
return true if input_pw.empty? && priv_protocol == :none
|
|
257
|
+
# Otherwise, if either one is missing, false!
|
|
258
|
+
return false if input_pw.empty? || priv_protocol == :none
|
|
259
|
+
# Otherwise, we have both input and configured passwords to compare
|
|
260
|
+
current_pw = priv_password
|
|
261
|
+
if current_pw.nil?
|
|
262
|
+
fail "SNMP user #{@name} #{@engine_id} has priv #{priv_protocol} " \
|
|
263
|
+
"but no password?\n" + @@node.show('show run snmp all')
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
if is_localized
|
|
267
|
+
# In this case, the password is already hashed.
|
|
268
|
+
hashed_pw = input_pw
|
|
269
|
+
else
|
|
252
270
|
# In this case passed in password is clear text while the running
|
|
253
|
-
# config is hashed value. We need to hash the
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
# config is hashed value. We need to hash the passed in clear text.
|
|
272
|
+
|
|
273
|
+
# Create dummy user
|
|
274
|
+
config_set('snmp_user', 'user', '', 'dummy_user', '',
|
|
275
|
+
"auth #{_auth_sym_to_str(auth_protocol)} #{input_pw}",
|
|
276
|
+
"priv #{_priv_sym_to_str(priv_protocol)} #{input_pw}",
|
|
277
|
+
'',
|
|
278
|
+
@engine_id.empty? ? '' : "engineID #{@engine_id}")
|
|
279
|
+
|
|
280
|
+
# Retrieve password hashes
|
|
281
|
+
dummyau = SnmpUser.auth_password('dummy_user', @engine_id)
|
|
282
|
+
hashed_pw = SnmpUser.priv_password('dummy_user', @engine_id)
|
|
283
|
+
if hashed_pw.nil?
|
|
284
|
+
fail "SNMP dummy user #{dummy_user} #{@engine_id} was configured " \
|
|
285
|
+
"but password is missing?\n" + @@node.show('show run snmp all')
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Delete dummy user
|
|
289
|
+
config_set('snmp_user', 'user', 'no', 'dummy_user', '',
|
|
290
|
+
"auth #{_auth_sym_to_str(auth_protocol)} #{dummyau}",
|
|
291
|
+
"priv #{_priv_sym_to_str(priv_protocol)} #{hashed_pw}",
|
|
292
|
+
'localizedkey',
|
|
293
|
+
@engine_id.empty? ? '' : "engineID #{@engine_id}")
|
|
294
|
+
end
|
|
295
|
+
hashed_pw == current_pw
|
|
273
296
|
end
|
|
274
|
-
return false if pw.nil? or dummypw.nil?
|
|
275
|
-
pw == dummypw
|
|
276
|
-
end
|
|
277
297
|
|
|
278
|
-
|
|
298
|
+
private
|
|
279
299
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
300
|
+
def _auth_sym_to_str(sym)
|
|
301
|
+
case sym
|
|
302
|
+
when :sha
|
|
303
|
+
return 'sha'
|
|
304
|
+
when :md5
|
|
305
|
+
return 'md5'
|
|
306
|
+
else
|
|
307
|
+
return ''
|
|
308
|
+
end
|
|
288
309
|
end
|
|
289
|
-
end
|
|
290
310
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
311
|
+
def _priv_sym_to_str(sym)
|
|
312
|
+
case sym
|
|
313
|
+
when :des
|
|
314
|
+
return '' # no protocol specified defaults to DES
|
|
315
|
+
when :aes128
|
|
316
|
+
return 'aes-128'
|
|
317
|
+
else
|
|
318
|
+
return ''
|
|
319
|
+
end
|
|
299
320
|
end
|
|
300
|
-
end
|
|
301
321
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
322
|
+
def _auth_str_to_sym(str)
|
|
323
|
+
SnmpUser._auth_str_to_sym(str)
|
|
324
|
+
end
|
|
305
325
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
326
|
+
# must be class method b/c it's used by default methods
|
|
327
|
+
def self._auth_str_to_sym(str)
|
|
328
|
+
case str
|
|
329
|
+
when /sha/i
|
|
330
|
+
return :sha
|
|
331
|
+
when /md5/i
|
|
332
|
+
return :md5
|
|
333
|
+
else
|
|
334
|
+
return :none
|
|
335
|
+
end
|
|
315
336
|
end
|
|
316
|
-
end
|
|
317
337
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
338
|
+
def _priv_str_to_sym(str)
|
|
339
|
+
SnmpUser._priv_str_to_sym(str)
|
|
340
|
+
end
|
|
321
341
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
342
|
+
def self._priv_str_to_sym(str)
|
|
343
|
+
case str
|
|
344
|
+
when /des/i
|
|
345
|
+
return :des
|
|
346
|
+
when /aes/i
|
|
347
|
+
return :aes128
|
|
348
|
+
else
|
|
349
|
+
return :none
|
|
350
|
+
end
|
|
330
351
|
end
|
|
331
|
-
end
|
|
332
352
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
353
|
+
def self._user_to_groups(user_hash)
|
|
354
|
+
return [] if user_hash.nil?
|
|
355
|
+
groups = user_hash['TABLE_groups']['ROW_groups'] unless
|
|
356
|
+
user_hash['TABLE_groups'].nil?
|
|
357
|
+
return [] if groups.nil?
|
|
358
|
+
groups = [groups] if groups.is_a?(Hash)
|
|
359
|
+
groups
|
|
360
|
+
end
|
|
340
361
|
end
|
|
341
362
|
end
|
|
342
|
-
end
|