2019-06-11 22:24:35 +00:00
|
|
|
require "helper"
|
|
|
|
require "inspec/resource"
|
|
|
|
require "resources/aws/aws_iam_user"
|
2017-02-01 21:27:10 +00:00
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
require "resource_support/aws"
|
|
|
|
require "resources/aws/aws_iam_user"
|
2019-05-21 00:19:38 +00:00
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
# MAUIB = MockAwsIamUserBackend
|
|
|
|
# Abbreviation not used outside this file
|
|
|
|
|
|
|
|
#=============================================================================#
|
|
|
|
# Constructor Tests
|
|
|
|
#=============================================================================#
|
|
|
|
class AwsIamUserConstructorTest < Minitest::Test
|
2017-07-05 20:31:27 +00:00
|
|
|
|
2017-02-01 21:27:10 +00:00
|
|
|
def setup
|
2017-12-08 18:34:09 +00:00
|
|
|
AwsIamUser::BackendFactory.select(MAIUB::Three)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_empty_params_throws_exception
|
|
|
|
assert_raises(ArgumentError) { AwsIamUser.new }
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_accepts_username_as_scalar
|
2019-06-11 22:24:35 +00:00
|
|
|
AwsIamUser.new("erin")
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_accepts_username_as_hash
|
2019-06-11 22:24:35 +00:00
|
|
|
AwsIamUser.new(username: "erin")
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_rejects_unrecognized_params
|
|
|
|
assert_raises(ArgumentError) { AwsIamUser.new(shoe_size: 9) }
|
2017-06-28 12:46:59 +00:00
|
|
|
end
|
2017-06-13 05:36:43 +00:00
|
|
|
end
|
2017-12-08 18:34:09 +00:00
|
|
|
|
|
|
|
#=============================================================================#
|
|
|
|
# Search / Recall
|
|
|
|
#=============================================================================#
|
|
|
|
class AwsIamUserRecallTest < Minitest::Test
|
|
|
|
def setup
|
|
|
|
AwsIamUser::BackendFactory.select(MAIUB::Three)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_search_miss_is_not_an_exception
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("tommy")
|
2017-12-08 18:34:09 +00:00
|
|
|
refute user.exists?
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_search_hit_via_scalar_works
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("erin")
|
2017-12-08 18:34:09 +00:00
|
|
|
assert user.exists?
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_equal("erin", user.username)
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_search_hit_via_hash_works
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new(username: "erin")
|
2017-12-08 18:34:09 +00:00
|
|
|
assert user.exists?
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_equal("erin", user.username)
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
#=============================================================================#
|
|
|
|
# Properties
|
|
|
|
#=============================================================================#
|
|
|
|
|
|
|
|
class AwsIamUserPropertiesTest < Minitest::Test
|
|
|
|
def setup
|
|
|
|
AwsIamUser::BackendFactory.select(MAIUB::Three)
|
|
|
|
end
|
|
|
|
|
2018-04-17 17:22:28 +00:00
|
|
|
def test_property_attached_policies
|
2019-06-11 22:24:35 +00:00
|
|
|
noone = AwsIamUser.new("nonesuch")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_empty(noone.attached_policy_names)
|
|
|
|
assert_empty(noone.attached_policy_arns)
|
2017-12-08 18:34:09 +00:00
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
erin = AwsIamUser.new("erin")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_empty(erin.attached_policy_names)
|
|
|
|
assert_empty(erin.attached_policy_arns)
|
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
leslie = AwsIamUser.new("leslie")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(1, leslie.attached_policy_names.count)
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_includes(leslie.attached_policy_names, "AdministratorAccess")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(1, leslie.attached_policy_arns.count)
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_includes(leslie.attached_policy_arns, "arn:aws:iam::aws:policy/AdministratorAccess")
|
2018-10-15 16:09:46 +00:00
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
jared = AwsIamUser.new("jared")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(2, jared.attached_policy_names.count)
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_includes(jared.attached_policy_names, "ReadOnlyAccess")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(2, jared.attached_policy_arns.count)
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_includes(jared.attached_policy_arns, "arn:aws:iam::aws:policy/ReadOnlyAccess")
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
2018-04-17 17:22:28 +00:00
|
|
|
def test_property_inline_policies
|
2019-06-11 22:24:35 +00:00
|
|
|
noone = AwsIamUser.new("nonesuch")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_empty(noone.inline_policy_names)
|
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
erin = AwsIamUser.new("erin")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_empty(erin.inline_policy_names)
|
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
leslie = AwsIamUser.new("leslie")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(2, leslie.inline_policy_names.count)
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_includes(leslie.inline_policy_names, "leslie-inline-01")
|
|
|
|
assert_includes(leslie.inline_policy_names, "leslie-inline-02")
|
2018-10-15 16:09:46 +00:00
|
|
|
|
2019-06-11 22:24:35 +00:00
|
|
|
jared = AwsIamUser.new("jared")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(1, jared.inline_policy_names.count)
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_includes(jared.inline_policy_names, "jared-inline-01")
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
#-----------------------------------------------------#
|
2018-04-17 17:22:28 +00:00
|
|
|
# username property
|
2017-12-08 18:34:09 +00:00
|
|
|
#-----------------------------------------------------#
|
2018-04-17 17:22:28 +00:00
|
|
|
def test_property_username_correct_on_hit
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("erin")
|
|
|
|
assert_equal("erin", user.username)
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
2018-04-17 17:22:28 +00:00
|
|
|
def test_property_username_correct_on_miss
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("nonesuch")
|
|
|
|
assert_equal("nonesuch", user.username)
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
2018-04-17 17:22:28 +00:00
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
#-----------------------------------------------------#
|
|
|
|
# access_keys property
|
|
|
|
#-----------------------------------------------------#
|
|
|
|
def test_property_access_keys_positive
|
2019-06-11 22:24:35 +00:00
|
|
|
keys = AwsIamUser.new("erin").access_keys
|
2017-12-08 18:34:09 +00:00
|
|
|
assert_kind_of(Array, keys)
|
|
|
|
assert_equal(keys.length, 2)
|
2018-10-15 16:09:46 +00:00
|
|
|
# We don't currently promise that the results
|
|
|
|
# will be InSpec resource objects.
|
|
|
|
# assert_kind_of(AwsIamAccessKey, keys.first)
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_property_access_keys_negative
|
2019-06-11 22:24:35 +00:00
|
|
|
keys = AwsIamUser.new("leslie").access_keys
|
2017-12-08 18:34:09 +00:00
|
|
|
assert_kind_of(Array, keys)
|
2018-10-15 16:09:46 +00:00
|
|
|
assert(keys.empty?)
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-17 17:22:28 +00:00
|
|
|
#=============================================================================#
|
|
|
|
# Matchers
|
|
|
|
#=============================================================================#
|
|
|
|
|
|
|
|
class AwsIamUserMatchersTest < Minitest::Test
|
|
|
|
def setup
|
|
|
|
AwsIamUser::BackendFactory.select(MAIUB::Three)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_matcher_mfa_positive
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("erin")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(true, user.has_mfa_enabled)
|
|
|
|
assert_equal(true, user.has_mfa_enabled?)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_matcher_mfa_negative
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("leslie")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(false, user.has_mfa_enabled)
|
|
|
|
assert_equal(false, user.has_mfa_enabled?)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_matcher_password_positive
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("erin")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(true, user.has_console_password)
|
|
|
|
assert_equal(true, user.has_console_password?)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_matcher_password_negative
|
2019-06-11 22:24:35 +00:00
|
|
|
user = AwsIamUser.new("leslie")
|
2018-04-17 17:22:28 +00:00
|
|
|
assert_equal(false, user.has_console_password)
|
|
|
|
assert_equal(false, user.has_console_password?)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_matcher_has_attached_policies
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_nil(AwsIamUser.new("nonesuch").has_attached_policies?)
|
|
|
|
refute(AwsIamUser.new("erin").has_attached_policies?)
|
|
|
|
assert(AwsIamUser.new("leslie").has_attached_policies?)
|
|
|
|
assert(AwsIamUser.new("jared").has_attached_policies?)
|
2018-04-17 17:22:28 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_matcher_has_inline_policies
|
2019-06-11 22:24:35 +00:00
|
|
|
assert_nil(AwsIamUser.new("nonesuch").has_inline_policies?)
|
|
|
|
refute(AwsIamUser.new("erin").has_inline_policies?)
|
|
|
|
assert(AwsIamUser.new("leslie").has_inline_policies?)
|
|
|
|
assert(AwsIamUser.new("jared").has_inline_policies?)
|
2018-04-17 17:22:28 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
#=============================================================================#
|
|
|
|
# Test Fixtures
|
|
|
|
#=============================================================================#
|
|
|
|
|
|
|
|
module MAIUB
|
2018-02-08 04:26:37 +00:00
|
|
|
class Three < AwsBackendBase
|
2017-12-08 18:34:09 +00:00
|
|
|
def get_user(criteria)
|
|
|
|
people = {
|
2019-06-11 22:24:35 +00:00
|
|
|
"erin" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
user: OpenStruct.new({
|
2018-10-15 16:09:46 +00:00
|
|
|
arn: "arn:aws:iam::123456789012:user/erin",
|
|
|
|
create_date: Time.parse("2016-09-21T23:03:13Z"),
|
|
|
|
path: "/",
|
|
|
|
user_id: "AKIAIOSFODNN7EXAERIN",
|
|
|
|
user_name: "erin",
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
"leslie" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
user: OpenStruct.new({
|
2018-10-15 16:09:46 +00:00
|
|
|
arn: "arn:aws:iam::123456789012:user/leslie",
|
|
|
|
create_date: Time.parse("2017-09-21T23:03:13Z"),
|
|
|
|
path: "/",
|
|
|
|
user_id: "AKIAIOSFODNN7EXAERIN",
|
|
|
|
user_name: "leslie",
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
"jared" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
user: OpenStruct.new({
|
2018-10-15 16:09:46 +00:00
|
|
|
arn: "arn:aws:iam::123456789012:user/jared",
|
|
|
|
create_date: Time.parse("2017-09-21T23:03:13Z"),
|
|
|
|
path: "/",
|
|
|
|
user_id: "AKIAIOSFODNN7EXAERIN",
|
|
|
|
user_name: "jared",
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
raise Aws::IAM::Errors::NoSuchEntity.new(nil, nil) unless people.key?(criteria[:user_name])
|
|
|
|
people[criteria[:user_name]]
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_login_profile(criteria)
|
|
|
|
# Leslie has no password
|
|
|
|
# Jared's is expired
|
|
|
|
people = {
|
2019-06-11 22:24:35 +00:00
|
|
|
"erin" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
login_profile: OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "erin",
|
2017-12-08 18:34:09 +00:00
|
|
|
password_reset_required: false,
|
|
|
|
create_date: Time.parse("2016-09-21T23:03:13Z"),
|
|
|
|
}),
|
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
"jared" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
login_profile: OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "jared",
|
2017-12-08 18:34:09 +00:00
|
|
|
password_reset_required: true,
|
|
|
|
create_date: Time.parse("2017-09-21T23:03:13Z"),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
raise Aws::IAM::Errors::NoSuchEntity.new(nil, nil) unless people.key?(criteria[:user_name])
|
|
|
|
people[criteria[:user_name]]
|
|
|
|
end
|
2019-06-11 22:24:35 +00:00
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
def list_mfa_devices(criteria)
|
|
|
|
# Erin has 2, one soft and one hw
|
|
|
|
# Leslie has none
|
|
|
|
# Jared has one soft
|
|
|
|
people = {
|
2019-06-11 22:24:35 +00:00
|
|
|
"erin" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
mfa_devices: [
|
|
|
|
OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "erin",
|
|
|
|
serial_number: "arn:blahblahblah",
|
2017-12-08 18:34:09 +00:00
|
|
|
enable_date: Time.parse("2016-09-21T23:03:13Z"),
|
|
|
|
}),
|
|
|
|
OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "erin",
|
|
|
|
serial_number: "1234567890",
|
2017-12-08 18:34:09 +00:00
|
|
|
enable_date: Time.parse("2016-09-21T23:03:13Z"),
|
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
],
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
"leslie" => OpenStruct.new({ mfa_devices: [] }),
|
|
|
|
"jared" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
mfa_devices: [
|
|
|
|
OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "jared",
|
|
|
|
serial_number: "arn:blahblahblah",
|
2017-12-08 18:34:09 +00:00
|
|
|
enable_date: Time.parse("2016-09-21T23:03:13Z"),
|
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
],
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
people[criteria[:user_name]]
|
|
|
|
end
|
2018-04-17 17:22:28 +00:00
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
def list_access_keys(criteria)
|
|
|
|
# Erin has 2
|
|
|
|
# Leslie has none
|
|
|
|
# Jared has one
|
|
|
|
people = {
|
2019-06-11 22:24:35 +00:00
|
|
|
"erin" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
access_key_metadata: [
|
|
|
|
OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "erin",
|
|
|
|
access_key_id: "AKIA111111111EXAMPLE",
|
2017-12-08 18:34:09 +00:00
|
|
|
create_date: Time.parse("2016-09-21T23:03:13Z"),
|
2019-06-11 22:24:35 +00:00
|
|
|
status: "Active",
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
|
|
|
OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "erin",
|
|
|
|
access_key_id: "AKIA222222222EXAMPLE",
|
2017-12-08 18:34:09 +00:00
|
|
|
create_date: Time.parse("2016-09-21T23:03:13Z"),
|
2019-06-11 22:24:35 +00:00
|
|
|
status: "Active",
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
],
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
"leslie" => OpenStruct.new({ access_key_metadata: [] }),
|
|
|
|
"jared" => OpenStruct.new({
|
2017-12-08 18:34:09 +00:00
|
|
|
access_key_metadata: [
|
|
|
|
OpenStruct.new({
|
2019-06-11 22:24:35 +00:00
|
|
|
user_name: "jared",
|
|
|
|
access_key_id: "AKIA3333333333EXAMPLE",
|
2017-12-08 18:34:09 +00:00
|
|
|
create_date: Time.parse("2017-10-21T23:03:13Z"),
|
2019-06-11 22:24:35 +00:00
|
|
|
status: "Active",
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
2019-06-11 22:24:35 +00:00
|
|
|
],
|
2017-12-08 18:34:09 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
people[criteria[:user_name]]
|
|
|
|
end
|
2018-04-17 17:22:28 +00:00
|
|
|
|
|
|
|
def list_user_policies(query)
|
|
|
|
people = {
|
2019-06-11 22:24:35 +00:00
|
|
|
"erin" => Aws::IAM::Types::ListUserPoliciesResponse.new(
|
2018-04-17 17:22:28 +00:00
|
|
|
policy_names: []
|
|
|
|
),
|
2019-06-11 22:24:35 +00:00
|
|
|
"leslie" => Aws::IAM::Types::ListUserPoliciesResponse.new(
|
|
|
|
policy_names: ["leslie-inline-01", "leslie-inline-02"]
|
|
|
|
),
|
|
|
|
"jared" => Aws::IAM::Types::ListUserPoliciesResponse.new(
|
|
|
|
policy_names: ["jared-inline-01"]
|
2018-10-15 16:09:46 +00:00
|
|
|
),
|
2018-04-17 17:22:28 +00:00
|
|
|
}
|
|
|
|
people[query[:user_name]]
|
|
|
|
end
|
|
|
|
|
|
|
|
def list_attached_user_policies(query)
|
|
|
|
people = {
|
2019-06-11 22:24:35 +00:00
|
|
|
"erin" => Aws::IAM::Types::ListAttachedUserPoliciesResponse.new(
|
|
|
|
attached_policies: []
|
2018-04-17 17:22:28 +00:00
|
|
|
),
|
2019-06-11 22:24:35 +00:00
|
|
|
"leslie" => Aws::IAM::Types::ListAttachedUserPoliciesResponse.new(
|
2018-04-17 17:22:28 +00:00
|
|
|
attached_policies: [
|
|
|
|
{
|
2019-06-11 22:24:35 +00:00
|
|
|
policy_arn: "arn:aws:iam::aws:policy/AdministratorAccess",
|
|
|
|
policy_name: "AdministratorAccess",
|
2018-04-17 17:22:28 +00:00
|
|
|
},
|
|
|
|
]
|
|
|
|
),
|
2019-06-11 22:24:35 +00:00
|
|
|
"jared" => Aws::IAM::Types::ListAttachedUserPoliciesResponse.new(
|
2018-04-17 17:22:28 +00:00
|
|
|
attached_policies: [
|
|
|
|
{
|
2019-06-11 22:24:35 +00:00
|
|
|
policy_arn: "arn:aws:iam::aws:policy/ReadOnlyAccess",
|
|
|
|
policy_name: "ReadOnlyAccess",
|
2018-04-17 17:22:28 +00:00
|
|
|
},
|
|
|
|
{
|
2019-06-11 22:24:35 +00:00
|
|
|
policy_arn: "arn:aws:iam::123456789012:policy/some-policy",
|
|
|
|
policy_name: "some-policy",
|
2018-04-17 17:22:28 +00:00
|
|
|
},
|
|
|
|
]
|
|
|
|
),
|
|
|
|
}
|
|
|
|
people[query[:user_name]]
|
|
|
|
end
|
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
2018-10-15 16:09:46 +00:00
|
|
|
end
|