classifieds 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f7a28ed7dd95a4865ff0f44347b3bd6c569e7286
4
+ data.tar.gz: 228092b3aaca67a48753c77d59540cb093a7b455
5
+ SHA512:
6
+ metadata.gz: fd48a3b29771b3e2153e58a740e156b09a3943303f0b70677e5a99b0e1f8f531a86e50ecaef7c2f63e30dab6357a88e1452ba7ef15444b7e7da4a6991916017d
7
+ data.tar.gz: 487ec5bbc7a0a70350189cceb1fcc7d0d8fa5482fd47a8a67ddae34a6d3bcba186fdbe0ee981ada576c1215d0707d70649ac7f5010dacd038f9c3f4ad5ed3ec1
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in classifieds.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 kaihar4
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,162 @@
1
+ # Classifieds
2
+
3
+ File Encryption Manager.
4
+
5
+ ## Description
6
+
7
+ Classifieds manages the encryption of files in the repository.
8
+
9
+ It is possible to securely encrypted using OpenSSL and also possible to manage them easily.
10
+
11
+ ## Installation
12
+
13
+ ```
14
+ $ gem install classifieds
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ Initialize classifieds.
20
+
21
+ ```
22
+ $ classifieds init
23
+ ```
24
+
25
+ Encrypt files which were described in .classifieds.
26
+
27
+ ```
28
+ $ classifieds encrypt
29
+ ```
30
+
31
+ Decrypt files which were described in .classifieds.
32
+
33
+ ```
34
+ $ classifieds decrypt
35
+ ```
36
+
37
+ Show a status of the encryption of this repository.
38
+
39
+ ```
40
+ $ classifieds status
41
+ ```
42
+
43
+ ## Example
44
+
45
+ In your repository:
46
+
47
+ ```
48
+ ├── bar
49
+ │   ├── bar1
50
+ │   └── bar2
51
+ ├── foo
52
+ ├── fuga
53
+ │   ├── fuga1
54
+ │   └── fuga2
55
+ └── hoge
56
+ ├── hoge1.rb
57
+ └── hoge2
58
+ ```
59
+
60
+ First, initialize classifieds.
61
+
62
+ ```
63
+ $ classifieds init
64
+ .classifieds was created
65
+
66
+ $ ls -a
67
+ .classifieds bar/ foo fuga/ hoge/
68
+ ```
69
+
70
+ Write files as relative path from `.classifieds` which you want to encrypt in `.classifieds`.
71
+
72
+ ```
73
+ $ vim .classifieds
74
+ bar/*
75
+ !bar/bar1
76
+ foo
77
+ fuga
78
+ */*.rb
79
+ ```
80
+
81
+ Show the status.
82
+
83
+ ```
84
+ $ classifieds status
85
+ Unencrypted:
86
+ /path/to/foo
87
+ /path/to/bar/bar2
88
+ /path/to/hoge/hoge1.rb
89
+ /path/to/fuga/fuga1
90
+ /path/to/fuga/fuga2
91
+ ```
92
+
93
+ Encrypt files.
94
+
95
+ ```
96
+ $ classifieds encrypt
97
+ Password:
98
+ Retype password:
99
+ Encrypted:
100
+ /path/to/foo
101
+ /path/to/bar/bar2
102
+ /path/to/hoge/hoge1.rb
103
+ /path/to/fuga/fuga1
104
+ /path/to/fuga/fuga2
105
+ ```
106
+
107
+ Check the status.
108
+
109
+ ```
110
+ $ classifieds status
111
+ Encrypted:
112
+ /path/to/foo
113
+ /path/to/bar/bar2
114
+ /path/to/hoge/hoge1.rb
115
+ /path/to/fuga/fuga1
116
+ /path/to/fuga/fuga2
117
+
118
+ $ cat foo
119
+ 65c0ec273963aacc69af593b03d1710ff90f75da¢É™¸
120
+ ```
121
+
122
+ Decrypt files.
123
+
124
+ ```
125
+ $ classifieds decrypt
126
+ Password:
127
+ Decrypted:
128
+ /path/to/foo
129
+ /path/to/bar/bar2
130
+ /path/to/hoge/hoge1.rb
131
+ /path/to/fuga/fuga1
132
+ /path/to/fuga/fuga2
133
+ ```
134
+
135
+ Check the status.
136
+
137
+ ```
138
+ $ classifieds status
139
+ Unencrypted:
140
+ /path/to/foo
141
+ /path/to/bar/bar2
142
+ /path/to/hoge/hoge1.rb
143
+ /path/to/fuga/fuga1
144
+ /path/to/fuga/fuga2
145
+
146
+ $ cat foo
147
+ foo
148
+ ```
149
+
150
+ ## Development
151
+
152
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
153
+
154
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
155
+
156
+ ## Contributing
157
+
158
+ 1. Fork it ( https://github.com/kaihar4/classifieds/fork )
159
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
160
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
161
+ 4. Push to the branch (`git push origin my-new-feature`)
162
+ 5. Create a new Pull Request
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'classifieds'
5
+
6
+ require 'irb'
7
+ IRB.start
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install --path vendor/bundle
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'classifieds/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'classifieds'
7
+ spec.version = Classifieds::VERSION
8
+ spec.authors = ["kaihar4"]
9
+ spec.email = ["0@kaihar4.com"]
10
+
11
+ spec.summary = 'File Encryption Manager'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/kaihar4/classifieds'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency 'thor'
22
+ spec.add_runtime_dependency 'safe_colorize'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.9'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'classifieds'
4
+
5
+ Classifieds::Main.start(ARGV)
@@ -0,0 +1,7 @@
1
+ require 'classifieds/exception'
2
+ require 'classifieds/parser'
3
+ require 'classifieds/main'
4
+
5
+ module Classifieds
6
+ SOURCE_FILE = '.classifieds'
7
+ end
@@ -0,0 +1,3 @@
1
+ module Classifieds
2
+ class ParseError < StandardError;end
3
+ end
@@ -0,0 +1,162 @@
1
+ require 'digest/sha1'
2
+ require 'openssl'
3
+ require 'fileutils'
4
+
5
+ require 'safe_colorize'
6
+
7
+ require 'thor'
8
+
9
+ module Classifieds
10
+ class Main < Thor
11
+ using SafeColorize
12
+
13
+ def initialize(*args)
14
+ @prefix = Digest::SHA1.hexdigest('classifieds')
15
+ super
16
+ end
17
+
18
+ desc 'init', 'Initialize classifieds'
19
+ def init
20
+ if File.exists?(SOURCE_FILE)
21
+ puts 'Classifieds already initialized'.color(:red)
22
+ else
23
+ FileUtils.touch(SOURCE_FILE)
24
+ puts "#{SOURCE_FILE} was created".color(:green)
25
+ end
26
+ end
27
+
28
+ desc 'encrypt', 'Encrypt files which were described in .classifieds'
29
+ def encrypt
30
+ @password ||= ask_password
31
+ retype_password
32
+
33
+ encrypted_files = classifieds.each_with_object([]) do |file_path, array|
34
+ next if encrypted?(file_path)
35
+
36
+ file = File.open(file_path, 'r+')
37
+ file.flock(File::LOCK_EX)
38
+
39
+ data = file.read.chomp
40
+
41
+ begin
42
+ encrypted = encrypt_data(data)
43
+ rescue ArgumentError
44
+ next
45
+ end
46
+
47
+ file.rewind
48
+ file.write @prefix + encrypted
49
+ file.truncate(file.tell)
50
+
51
+ array << file_path
52
+ end
53
+
54
+ puts 'Encrypted:'.color(:green) unless encrypted_files.empty?
55
+ encrypted_files.each {|encrypted_file| puts "\t" + encrypted_file }
56
+ end
57
+
58
+ desc 'decrypt', 'Decrypt files which were described in .classifieds'
59
+ def decrypt
60
+ @password ||= ask_password
61
+
62
+ decrypted_files = classifieds.each_with_object([]) do |file_path, array|
63
+ next if decrypted?(file_path)
64
+
65
+ file = File.open(file_path, 'r+')
66
+ file.flock(File::LOCK_EX)
67
+
68
+ file.read(@prefix.size)
69
+ data = file.read.chomp
70
+
71
+ begin
72
+ decrypted = decrypt_data(data)
73
+ rescue OpenSSL::Cipher::CipherError
74
+ STDERR.puts 'The entered password is wrong: '.color(:red) + file_path
75
+ next
76
+ end
77
+
78
+ file.rewind
79
+ file.write decrypted
80
+ file.truncate(file.tell)
81
+
82
+ array << file_path
83
+ end
84
+
85
+ puts 'Decrypted:'.color(:green) unless decrypted_files.empty?
86
+ decrypted_files.each {|decrypted_file| puts "\t" + decrypted_file }
87
+ end
88
+
89
+ desc 'status', 'Show a status of the encryption of this repository'
90
+ def status
91
+ encrypted_files = []
92
+ unencrypted_files = []
93
+
94
+ classifieds.each do |file|
95
+ if encrypted?(file)
96
+ encrypted_files << file
97
+ else
98
+ unencrypted_files << file
99
+ end
100
+ end
101
+ puts 'Encrypted:'.color(:green) unless encrypted_files.empty?
102
+ encrypted_files.each {|file| puts "\t" + file }
103
+ puts 'Unencrypted:'.color(:red) unless unencrypted_files.empty?
104
+ unencrypted_files.each {|file| puts "\t" + file }
105
+ end
106
+
107
+ private
108
+
109
+ def ask_password
110
+ print 'Password: '
111
+ password = STDIN.noecho(&:gets)
112
+ print "\n"
113
+
114
+ password
115
+ end
116
+
117
+ def retype_password
118
+ print 'Retype password: '
119
+ password = STDIN.noecho(&:gets)
120
+ print "\n"
121
+
122
+ unless password == @password
123
+ STDERR.puts 'Sorry, passwords do not match'.color(:red)
124
+ @password = ask_password
125
+ retype_password
126
+ end
127
+ end
128
+
129
+ def classifieds
130
+ File.open(SOURCE_FILE) do |f|
131
+ Parser.parse(f.read)
132
+ end
133
+ rescue Errno::ENOENT
134
+ STDERR.puts "#{SOURCE_FILE} is not found".color(:red)
135
+ exit 1
136
+ end
137
+
138
+ def encrypt_data(data)
139
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
140
+ cipher.encrypt
141
+ cipher.pkcs5_keyivgen(@password)
142
+ cipher.update(data) + cipher.final
143
+ end
144
+
145
+ def decrypt_data(data)
146
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
147
+ cipher.decrypt
148
+ cipher.pkcs5_keyivgen(@password)
149
+ cipher.update(data) + cipher.final
150
+ end
151
+
152
+ def encrypted?(file)
153
+ File.open(file, 'r') do |f|
154
+ !!f.read(@prefix.size).to_s.match(/\A#{@prefix}\z/)
155
+ end
156
+ end
157
+
158
+ def decrypted?(file)
159
+ !encrypted?(file)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,64 @@
1
+ module Classifieds
2
+ class Parser
3
+ def self.parse(text)
4
+ lines = text.each_line.map(&:chomp)
5
+ result = lines.each_with_object([]) do |line, array|
6
+ target = detect_target(line)
7
+
8
+ case target
9
+ when Array
10
+ if classifieds_ignore?(line)
11
+ target.each do |file|
12
+ array.delete(file)
13
+ end
14
+ else
15
+ array.concat(target)
16
+ end
17
+ when String
18
+ if classifieds_ignore?(line)
19
+ array.delete(target)
20
+ else
21
+ array << target
22
+ end
23
+ end
24
+ end
25
+
26
+ result
27
+ end
28
+
29
+ def self.detect_target(string)
30
+ path = string.match(/^!?(.+)/)[1]
31
+ absolute_path = File.join(Dir.pwd, path)
32
+
33
+ # Dir.glob notation
34
+ if absolute_path.include?('*')
35
+ recursive_glob(absolute_path)
36
+ else
37
+ case File.ftype(path)
38
+ when 'file'
39
+ absolute_path
40
+ when 'directory'
41
+ recursive_glob(File.join(absolute_path, '*'))
42
+ else
43
+ raise ParseError, "invalid file type: #{string}"
44
+ end
45
+ end
46
+ rescue Errno::ENOENT
47
+ end
48
+
49
+ def self.classifieds_ignore?(string)
50
+ string.start_with?('!')
51
+ end
52
+
53
+ def self.recursive_glob(pattern)
54
+ Dir.glob(pattern).each_with_object([]) do |path, array|
55
+ case File.ftype(path)
56
+ when 'file'
57
+ array << path
58
+ when 'directory'
59
+ array.concat(recursive_glob(File.join(path, '*')))
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module Classifieds
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: classifieds
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - kaihar4
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: safe_colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: File Encryption Manager
70
+ email:
71
+ - 0@kaihar4.com
72
+ executables:
73
+ - classifieds
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - classifieds.gemspec
85
+ - exe/classifieds
86
+ - lib/classifieds.rb
87
+ - lib/classifieds/exception.rb
88
+ - lib/classifieds/main.rb
89
+ - lib/classifieds/parser.rb
90
+ - lib/classifieds/version.rb
91
+ homepage: https://github.com/kaihar4/classifieds
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.4.5
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: File Encryption Manager
115
+ test_files: []