password_expiration_notifier 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 899afe284850302b370bd0dceb6d95958af4e502
4
+ data.tar.gz: 7c52076152fb3f104e781c4b4c80c5b0090cc21f
5
+ SHA512:
6
+ metadata.gz: 8590de419afac472a17a94258de6f015cc570c03f516362c476201a380ae7df0b22e49cd1b99cd348cc2ca4fa2bbf96323cf97b79255de52a54f0c4cf7c34c50
7
+ data.tar.gz: 06aba3cab05436bf3779ae17c8eb4cd71358abf2cc75f1972ecb4f2c23c5bbcecb7c5490cfb368674eba547515cfac7b3cabc261b857455b4e983be77f3a2914
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /vendor/
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
16
+ notifier.config
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in password_expire_notify.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 ISOBE Kazuhiko
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # PasswordExpirationNotifier
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'password_expiration_notifier'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install password_expiration_notifier
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/muramasa64/password_expiration_notifier/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'password_expiration_notifier'
4
+
5
+ PasswordExpirationNotifier::CLI.start
@@ -0,0 +1,5 @@
1
+ require "password_expiration_notifier/version"
2
+ require 'password_expiration_notifier/cli'
3
+ require 'password_expiration_notifier/ldap'
4
+ require 'password_expiration_notifier/utils'
5
+ require 'password_expiration_notifier/slack'
@@ -0,0 +1,70 @@
1
+ require 'password_expiration_notifier'
2
+ require 'password_expiration_notifier/utils'
3
+ require 'thor'
4
+
5
+ module PasswordExpirationNotifier
6
+ class CLI < Thor
7
+ include Utils
8
+ class_option :config,
9
+ aliases: [:c],
10
+ type: :string,
11
+ desc: "use config file",
12
+ default: "notifier.config"
13
+ class_option :debug,
14
+ type: :boolean,
15
+ desc: "debug mode"
16
+
17
+ desc "list [--all] [--expire-within=DAYS]", "show account list of Active Directory"
18
+ option :all,
19
+ aliases: [:a],
20
+ type: :boolean,
21
+ desc: "show all accounts"
22
+ option :expire_within,
23
+ aliases: [:e],
24
+ type: :numeric,
25
+ desc: "expire within n days",
26
+ default: 7
27
+ option :expiration_days,
28
+ aliases: [:d],
29
+ type: :numeric,
30
+ desc: "password expiration days",
31
+ default: 90
32
+ option :exclude_expired,
33
+ aliases: [:x],
34
+ type: :boolean,
35
+ desc: "exclude password expiration",
36
+ default: true
37
+ def list()
38
+ users = fetch_users(config(options))
39
+ show_list(users)
40
+ end
41
+
42
+ desc "notify [--expire-within=DAYS] [--dry-run]", "send notify mail to the account it expires"
43
+ option :expire_within,
44
+ aliases: [:e],
45
+ type: :numeric,
46
+ desc: "expire within n days",
47
+ default: 7
48
+ option :expiration_days,
49
+ aliases: [:d],
50
+ type: :numeric,
51
+ desc: "password expiration days",
52
+ default: 90
53
+ option :dry_run,
54
+ type: :boolean,
55
+ desc: "show list only. do not sent notify"
56
+ option :exclude_expired,
57
+ aliases: [:x],
58
+ type: :boolean,
59
+ desc: "exclude password expiration",
60
+ default: true
61
+ def notify()
62
+ conf = config(options)
63
+ users = fetch_users(conf)
64
+ slack = PasswordExpirationNotifier::Slack.new(conf)
65
+ users.each do |user|
66
+ slack.notify_to(user)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,38 @@
1
+ require 'password_expiration_notifier'
2
+ require 'ostruct'
3
+ require 'yaml'
4
+
5
+ module PasswordExpirationNotifier
6
+ class Config < OpenStruct
7
+ def initialize(options = nil)
8
+ @table = {}
9
+ @hash_table = {}
10
+
11
+ if options
12
+ if options[:config]
13
+ conf = YAML.load_file(options[:config])
14
+ @table, @hash_table = load_config(conf)
15
+ end
16
+
17
+ @table, @hash_table = load_config(options, @table, @hash_table)
18
+ end
19
+ end
20
+
21
+ def to_h
22
+ @hash_table
23
+ end
24
+
25
+ private
26
+ def load_config(conf, table = {}, hash_table = {})
27
+ table = table.dup
28
+ hash_table = hash_table.dup
29
+ conf.each do |k,v|
30
+ table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
31
+ hash_table[k.to_sym] = v
32
+
33
+ new_ostruct_member(k)
34
+ end
35
+ [table, hash_table]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,50 @@
1
+ require 'password_expiration_notifier'
2
+ require 'net/ldap'
3
+
4
+ module PasswordExpirationNotifier
5
+ class LDAP
6
+ def initialize(conf)
7
+ opt = conf.ldap.to_h
8
+ opt[:auth] = {
9
+ username: conf.ldap.user,
10
+ password: conf.ldap.password,
11
+ method: :simple
12
+ }
13
+ @filters = []
14
+ @connection = Net::LDAP.new(opt)
15
+ end
16
+
17
+ def add_filter(key, value)
18
+ @filters << Net::LDAP::Filter.eq(key, value)
19
+ end
20
+
21
+ def users(key = nil, value = nil)
22
+ users = {}
23
+ if @connection.bind
24
+ filter = @filters.inject {|f1, f2| f1 & f2}
25
+ @connection.search(filter: filter, return_result: false) do |entry|
26
+ # UserAccountControl flag 0x0002 => ACCOUNTDISABLE
27
+ # see https://support.microsoft.com/en-us/kb/305144
28
+ next unless entry['UserAccountControl'].first.to_i & 0x0002 == 0
29
+ entries = {}
30
+ entry.each do |attr, values|
31
+ entries[attr] = values.size == 1 ? values.first : values
32
+ end
33
+ users[entry['sAMAccountName'].first] = entries
34
+ end
35
+ end
36
+ users.map do |k,v|
37
+ v[:pwdlastset] = LDAP.windows_time_to_ruby_time(v[:pwdlastset])
38
+ [k, v]
39
+ end
40
+ end
41
+
42
+ class << self
43
+ def windows_time_to_ruby_time(windows_time)
44
+ unix_time = (windows_time.to_i)/10000000-11644473600
45
+ Time.at(unix_time)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,27 @@
1
+ require 'password_expiration_notifier'
2
+ require 'slack-notify'
3
+
4
+ module PasswordExpirationNotifier
5
+ class Slack
6
+ def initialize(conf)
7
+ @conf = conf
8
+ end
9
+
10
+ def notify_to(user)
11
+ attr = user.last
12
+ client = SlackNotify::Client.new(
13
+ webhook_url: @conf.slack.webhook_url,
14
+ channel: "@#{attr[:samaccountname]}",
15
+ icon_url: @conf.slack.icon_url,
16
+ icon_emoji: @conf.slack.icon_emoji,
17
+ link_names: @conf.slack.link_names
18
+ )
19
+ message = "Your domain account #{attr[:samaccountname]}'s password expire at #{attr[:expire_at]}. Please update your password."
20
+ if @conf.dry_run
21
+ puts message
22
+ else
23
+ client.notify(message)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,73 @@
1
+ require 'password_expiration_notifier'
2
+ require 'password_expiration_notifier/ldap'
3
+ require 'password_expiration_notifier/config'
4
+ require 'date'
5
+
6
+ module PasswordExpirationNotifier
7
+ module Utils
8
+
9
+ def now
10
+ @now ||= Time.now
11
+ end
12
+
13
+ def remaining(password_last_set_t, expiration_days)
14
+ -((now - password_last_set_t).divmod(24*60*60)[0] - expiration_days)
15
+ end
16
+
17
+ def expire_at(password_last_set_t, expiration_days)
18
+ password_last_set_t + (24 * 60 * 60 * expiration_days)
19
+ end
20
+
21
+ def expire_soon?(remaining, expire_within)
22
+ remaining <= 0 || remaining <= expire_within
23
+ end
24
+
25
+ def expired?(remaining, expire_within)
26
+ remaining <= 0 || remaining > expire_within
27
+ end
28
+
29
+ def show_list(users)
30
+ puts "username\tdescription\texpire_at\tremaining"
31
+ users.sort_by {|user| user[1][:remaining] }.each do |user|
32
+ attr = user[1]
33
+ puts "#{attr[:samaccountname]}\t#{attr[:description]}\t#{attr[:expire_at]}\t#{attr[:remaining]}"
34
+ end
35
+ end
36
+
37
+ def select(attr, conf)
38
+ if options[:all]
39
+ attr
40
+ else
41
+ if conf.exclude_expired && !expired?(attr[:remaining], conf.expire_within)
42
+ attr
43
+ end
44
+ if expire_soon?(attr[:remaining], conf.expire_within)
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ def fetch_users(conf)
51
+ ad = PasswordExpirationNotifier::LDAP.new(conf)
52
+ ad.add_filter('objectClass', 'user')
53
+ if conf.ldap.filterkey && conf.ldap.filtervalue
54
+ ad.add_filter(conf.ldap.filterkey, conf.ldap.filtervalue)
55
+ end
56
+
57
+ selected_users = {}
58
+ ad.users.each do |user, attr|
59
+ attr[:remaining] = remaining(attr[:pwdlastset], conf.expiration_days)
60
+ attr[:expire_at] = expire_at(attr[:pwdlastset], conf.expiration_days)
61
+ if options[:all] || expire_soon?(attr[:remaining], conf.expire_within)
62
+ next if conf.exclude_expired && attr[:remaining] <= 0
63
+ selected_users[user] = attr
64
+ end
65
+ end
66
+ selected_users
67
+ end
68
+
69
+ def config(options)
70
+ PasswordExpirationNotifier::Config.new(options)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ module PasswordExpirationNotifier
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,15 @@
1
+ ---
2
+ expire_within: 7
3
+ expiration_days: 20
4
+ ldap:
5
+ user: "user@example.local"
6
+ password: "user password"
7
+ base: "dc=example,dc=local"
8
+ host: "ldap.example.local"
9
+ port: 389
10
+ filterkey: "filter key"
11
+ filtervalue: "filter value"
12
+ slack:
13
+ webhook_url: "webhook_url"
14
+ icon_url: "icon_url"
15
+ icon_emoji: ":icon_emoji:"
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'password_expiration_notifier/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "password_expiration_notifier"
8
+ spec.version = PasswordExpirationNotifier::VERSION
9
+ spec.authors = ["ISOBE Kazuhiko"]
10
+ spec.email = ["muramasa64@gmail.com"]
11
+ spec.summary = %q{Notify the password expiration date of the Active Directory account}
12
+ spec.description = %q{Notify the password expiration date of the Active Directory account}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency "thor"
25
+ spec.add_dependency "activeldap"
26
+ spec.add_dependency "net-ldap"
27
+ spec.add_dependency "slack-notify"
28
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: password_expiration_notifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ISOBE Kazuhiko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: activeldap
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
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: net-ldap
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: slack-notify
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Notify the password expiration date of the Active Directory account
98
+ email:
99
+ - muramasa64@gmail.com
100
+ executables:
101
+ - password_expiration_notifier
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - bin/password_expiration_notifier
111
+ - lib/password_expiration_notifier.rb
112
+ - lib/password_expiration_notifier/cli.rb
113
+ - lib/password_expiration_notifier/config.rb
114
+ - lib/password_expiration_notifier/ldap.rb
115
+ - lib/password_expiration_notifier/slack.rb
116
+ - lib/password_expiration_notifier/utils.rb
117
+ - lib/password_expiration_notifier/version.rb
118
+ - notifier.config.sample
119
+ - password_expiration_notifier.gemspec
120
+ homepage: ''
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.4.5
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Notify the password expiration date of the Active Directory account
144
+ test_files: []