vidibus-pureftpd 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,43 +1,69 @@
1
1
  # Vidibus::Pureftpd
2
2
 
3
- Allows control of [Pure-FTPd](http://www.pureftpd.org/project/pure-ftpd), the free, secure, production-quality and standard-conformant FTP server.
3
+ Provides an ActiveModel-based abstraction of Pure-FTPd's [virtual users](http://download.pureftpd.org/pub/pure-ftpd/doc/README.Virtual-Users). [Pure-FTPd](http://www.pureftpd.org/project/pure-ftpd) is a free, secure, production-quality and standard-conformant FTP server.
4
4
 
5
- This gem is part of [Vidibus](http://vidibus.org), an open source toolset for building distributed (video) applications.
6
-
7
- ## Installation
8
-
9
- Add the dependency to the Gemfile of your application: `gem 'vidibus-pureftpd'`. Then call bundle install on your console.
5
+ This gem is part of [Vidibus](http://vidibus.org), an open source toolset for building distributed (video) applications. It has been tested with Ruby 1.8.7 and 1.9.3.
10
6
 
11
7
 
12
8
  ## Usage
13
9
 
14
- Add a user:
10
+ Basic CRUD operations are available for `Vidibus::Pureftpd::User`:
15
11
 
16
- ```
17
- Vidibus::Pureftpd.add_user({
18
- :login => 'someuser',
12
+ ```ruby
13
+ user = Vidibus::Pureftpd::User.new(
14
+ :login => 'my_user',
19
15
  :password => 'verysecret',
20
- :directory => '/tmp'
21
- })
22
- ```
16
+ :directory => '/home/my_user'
17
+ )
18
+ # => returns a new user instance
23
19
 
24
- Delete a user:
20
+ user = Vidibus::Pureftpd::User.create(
21
+ :login => 'my_user',
22
+ :password => 'verysecret',
23
+ :directory => '/home/my_user'
24
+ )
25
+ # => creates a new user and returns the instance
25
26
 
27
+ user = Vidibus::Pureftpd::User.find_by_login('my_user')
28
+ # => reads user from database and returns an user instance
29
+
30
+ user.save
31
+ # => saves user to database and returns true
32
+
33
+ user.destroy
34
+ # => removes user from database
26
35
  ```
27
- Vidibus::Pureftpd.delete_user(:login => 'someuser')
28
- ```
29
36
 
30
- Change a user's password:
37
+ Additionally, some methods are provided for your convenience:
38
+
39
+ ```ruby
40
+ user.valid?
41
+ # => returns true if user is valid
42
+
43
+ user.persisted?
44
+ # => returns true if user has been saved to database
31
45
 
46
+ user.reload
47
+ # => reloads user from database
32
48
  ```
33
- Vidibus::Pureftpd.change_password({
34
- :login => 'someuser',
35
- :password => 'whatever'
36
- })
49
+
50
+ By default, `Vidibus::Pureftpd` will use these settings which you may override:
51
+
52
+ ```ruby
53
+ Vidibus::Pureftpd.settings[:sysuser] # => 'pureftpd_user'
54
+ Vidibus::Pureftpd.settings[:sysgroup] # => 'pureftpd_group'
55
+ Vidibus::Pureftpd.settings[:password_file] # => '/etc/pure-ftpd/pureftpd.passwd'
37
56
  ```
38
57
 
39
58
 
40
- ## Install Pure-FTPd on Debian Lenny
59
+ ## Installation
60
+
61
+ Add the dependency to the Gemfile of your application: `gem 'vidibus-pureftpd'`. Then call bundle install on your console.
62
+
63
+ Installation of the Pure-FTPd server itself is quite simple:
64
+
65
+
66
+ ### Install Pure-FTPd on Debian Lenny
41
67
 
42
68
  Get the package:
43
69
 
@@ -112,13 +138,13 @@ Finally, (re)start Pure-FTPd:
112
138
  For more instructions, please [check this resource](http://linux.justinhartman.com/PureFTPd_Installation_and_Setup).
113
139
 
114
140
 
115
- ## Install Pure-FTPd on OSX for testing
141
+ ### Install Pure-FTPd on OSX (required for testing this gem)
116
142
 
117
143
  ```
118
144
  brew install pure-ftpd
119
145
  ```
120
146
 
121
- Create the user `pureftpd_user` with ID 483:
147
+ In order to perform the tests, a certain user is required. Create the user `pureftpd_user` with ID 483:
122
148
 
123
149
  ```
124
150
  sudo dscl . create /Users/pureftpd_user uid 483
@@ -127,7 +153,7 @@ sudo dscl . create /Users/pureftpd_user UserShell /etc/pure-ftpd
127
153
  sudo dscl . create /Users/pureftpd_user NFSHomeDirectory /dev/null
128
154
  ```
129
155
 
130
- Create the group `pureftpd_group` with ID 483:
156
+ Then create the group `pureftpd_group`, also with ID 483:
131
157
 
132
158
  ```
133
159
  sudo dscl . create /Groups/pureftpd_group gid 483
@@ -141,21 +167,14 @@ dscacheutil -q user
141
167
  dscacheutil -q group
142
168
  ```
143
169
 
144
- Ensure that the database exists and is writable for the user that executes RSpec:
145
-
146
- ```
147
- sudo mkdir /etc/pure-ftpd/
148
- sudo touch /etc/pure-ftpd/pureftpd.passwd
149
- sudo chown -R `whoami` /etc/pure-ftpd/
150
- ```
151
170
 
152
- To start the server (not needed for testing), type:
171
+ Not needed for testing, but to start the server, type:
153
172
 
154
173
  ```
155
174
  sudo /usr/local/sbin/pure-ftpd &
156
175
  ```
157
176
 
158
- You should be able to connect via ftp:
177
+ You should now be able to connect via ftp:
159
178
 
160
179
  ```
161
180
  ftp localhost
@@ -164,7 +183,27 @@ ftp localhost
164
183
  Shut it down with:
165
184
 
166
185
  ```
167
- pkill pure-ftpd
186
+ sudo pkill pure-ftpd
187
+ ```
188
+
189
+
190
+ ## TODO
191
+
192
+ Implement all user options offered by Pure-FTPd:
193
+
194
+ ```
195
+ -t <download bandwidth>
196
+ -T <upload bandwidth>
197
+ -n <max number of files>
198
+ -N <max Mbytes>
199
+ -q <upload ratio>
200
+ -Q <download ratio>
201
+ -r <allow client host>
202
+ -R <deny client host>
203
+ -i <allow local host>
204
+ -I <deny local host>
205
+ -y <max number of concurrent sessions>
206
+ -z <hhmm>-<hhmm>
168
207
  ```
169
208
 
170
209
 
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rdoc/task'
5
5
  require 'rspec'
6
6
  require 'rspec/core/rake_task'
7
7
 
8
- require 'vidibus/pureftpd'
8
+ require 'vidibus/pureftpd/version'
9
9
 
10
10
  Bundler::GemHelper.install_tasks
11
11
 
@@ -0,0 +1,8 @@
1
+ de:
2
+ errors:
3
+ messages:
4
+ taken: 'ist bereits vergeben'
5
+ not_existent: 'gibt es nicht'
6
+ not_a_directory: 'muss ein Verzeichnis sein'
7
+ not_readable: 'muss lesbar sein'
8
+ not_writable: 'muss beschreibbar sein'
@@ -0,0 +1,8 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ taken: 'has already been taken'
5
+ not_existent: 'does not exist'
6
+ not_a_directory: 'must be a directory'
7
+ not_readable: 'must be readable'
8
+ not_writable: 'must be writable'
@@ -1,4 +1,6 @@
1
- require 'open3'
1
+ require 'posix/spawn'
2
+ require 'active_model'
2
3
  require 'vidibus-core_extensions'
3
4
 
4
5
  require 'vidibus/pureftpd'
6
+ require 'vidibus/pureftpd/user'
@@ -1,7 +1,5 @@
1
1
  module Vidibus
2
2
  module Pureftpd
3
- VERSION = '0.1.0'
4
-
5
3
  class Error < StandardError; end
6
4
 
7
5
  class << self
@@ -17,60 +15,25 @@ module Vidibus
17
15
  }
18
16
  end
19
17
 
20
- # Adds a new user.
21
- # Required options:
22
- # :login, :password, :directory
23
- def add_user(options)
24
- unless options.keys?(:login, :password, :directory)
25
- raise ArgumentError.new('Required options are :login, :password, :directory')
26
- end
27
- password = options.delete(:password)
28
- cmd = 'pure-pw useradd %{login} -f %{password_file} -u %{sysuser} -g %{sysgroup} -d %{directory} -m' % settings.merge(options)
29
- perform(cmd) do |stdin, stdout, stderr|
30
- stdin.puts(password)
31
- stdin.puts(password)
32
- end
33
- end
34
-
35
- # Deletes an existing user.
36
- # Required options:
37
- # :login
38
- def delete_user(options)
39
- unless options.key?(:login)
40
- raise ArgumentError.new('Required option is :login')
41
- end
42
- cmd = 'pure-pw userdel %{login} -f %{password_file} -m' % settings.merge(options)
43
- perform(cmd)
44
- end
45
-
46
- # Changes password of existing user.
47
- # Required options:
48
- # :login, :password
49
- def change_password(options)
50
- unless options.keys?(:login, :password)
51
- raise ArgumentError.new('Required options are :login, :password')
52
- end
53
- password = options.delete(:password)
54
- cmd = 'pure-pw passwd %{login} -f %{password_file} -m' % settings.merge(options)
55
- perform(cmd) do |stdin, stdout, stderr|
56
- stdin.puts(password)
57
- stdin.puts(password)
58
- end
59
- end
60
-
61
- protected
62
-
63
- # Performs given command. Accepts a block with |stdin, stdout, stderr|.
18
+ # Performs given command prefixed with pure-pw.
19
+ # Accepts a block with |stdin, stdout, stderr|.
64
20
  def perform(cmd, &block)
21
+ cmd = "pure-pw #{cmd}"
65
22
  error = ''
66
- Open3.popen3(cmd) do |stdin, stdout, stderr|
67
- yield(stdin, stdout, stderr) if block_given?
68
- error = stderr.read
69
- end
23
+ output = nil
24
+
25
+ pid, stdin, stdout, stderr = POSIX::Spawn::popen4(cmd)
26
+ yield(stdin, stdout, stderr) if block_given?
27
+ error = stderr.read
28
+ output = stdout.read
29
+
70
30
  unless error == ''
71
- raise Error.new("Error while executing this command:\n#{cmd}\n\n#{error}")
31
+ raise Error.new("Pure-FTPd returned an error:\n#{cmd}\n\n#{error}")
72
32
  end
73
- true
33
+ output
34
+ ensure
35
+ [stdin, stdout, stderr].each { |io| io.close if !io.closed? }
36
+ Process::waitpid(pid)
74
37
  end
75
38
  end
76
39
  end
@@ -4,27 +4,56 @@ module Vidibus
4
4
  include ActiveModel::Validations
5
5
  include ActiveModel::Dirty
6
6
 
7
- attr_accessor :login, :password, :directory
7
+ class Error < Vidibus::Pureftpd::Error; end
8
+ class DocumentNotFound < Error; end
9
+
10
+ ACCESSSORS = [:login, :password, :directory]
11
+
12
+ ACCESSSORS.each do |attribute|
13
+ class_eval <<-EOS
14
+ def #{attribute}=(value)
15
+ unless @#{attribute} == value
16
+ #{attribute}_will_change!
17
+ @#{attribute} = value
18
+ end
19
+ end
20
+
21
+ def #{attribute}
22
+ @#{attribute}
23
+ end
24
+ EOS
25
+ end
8
26
 
9
- validate :login, :password, :directory, :presence => true
10
- validates :unique_login
27
+ define_attribute_methods ACCESSSORS
28
+
29
+ validates :password, :directory, :presence => true
30
+ validates :login, :format => { :with => /^[a-z_]+$/ }
31
+ validate :unique_login?, :if => :login_changed?
32
+ validate :valid_directory?, :if => :directory_changed?
11
33
 
12
34
  def initialize(values = {})
13
- attributes = values
35
+ self.attributes = values
36
+ @persisted = false
14
37
  end
15
38
 
16
39
  def save
40
+ return false unless valid? && changed?
17
41
  persisted? ? update : create
42
+ @previously_changed = changes
43
+ @changed_attributes.clear
44
+ @persisted = true
18
45
  end
19
46
 
20
47
  def destroy
21
- cmd = 'userdel %{login} -f %{password_file} -m' % settings
48
+ return false unless persisted? && login_was
49
+ cmd = "userdel #{login_was} -f %{password_file} -m" % settings
22
50
  perform(cmd)
23
- user.instance_variable_set('@is_persisted', false)
51
+ instance_variable_set('@persisted', false)
52
+ self
24
53
  end
25
54
 
26
55
  def persisted?
27
- @is_persisted || User.find(login)
56
+ @persisted
28
57
  end
29
58
 
30
59
  def attributes
@@ -35,50 +64,91 @@ module Vidibus
35
64
  hash
36
65
  end
37
66
 
38
- def attributes=(values)
39
- values.each {|a| self.send("#{a}=")}
67
+ def attributes=(hash)
68
+ hash.each do |key, value|
69
+ send("#{key}=", value)
70
+ end
40
71
  end
41
72
 
42
73
  def reload
43
- attributes = User.find(login).attributes
44
- end
45
-
46
- def settings
47
- attributes.merge(Vidibus::Pureftpd.settings)
74
+ if persisted? && login_was && (user = User.find_by_login(login_was))
75
+ self.attributes = user.attributes
76
+ self
77
+ else
78
+ raise DocumentNotFound
79
+ end
48
80
  end
49
81
 
50
82
  class << self
51
83
 
52
- def find(login)
53
- cmd = 'show %{login} -f %{password_file}' % settings
54
- data = Vidibus::Pureftpd.perform(cmd)
84
+ def find_by_login(login)
85
+ cmd = "show #{login} -f %{password_file}" %
86
+ Vidibus::Pureftpd.settings
87
+ begin
88
+ data = Vidibus::Pureftpd.perform(cmd)
89
+ rescue Vidibus::Pureftpd::Error => e
90
+ if e.message.match('Unable to fetch info about user')
91
+ return nil
92
+ else
93
+ raise e
94
+ end
95
+ end
96
+
55
97
  attributes = {}
98
+ data.scan(/(#{ACCESSSORS.join('|')})\s*:\s*(.+)/i).each do |key, value|
99
+ attributes[key.downcase] = value
100
+ end
101
+
56
102
  User.new(attributes).tap do |user|
57
- user.instance_variable_set('@is_persisted', true)
103
+ user.instance_variable_set('@persisted', true)
58
104
  end
59
105
  end
60
106
 
61
107
  def create(attributes)
62
-
108
+ new(attributes).tap do |user|
109
+ user.save
110
+ end
63
111
  end
64
112
  end
65
113
 
66
114
  private
67
115
 
68
- def unique_login
69
- puts %(login = #{login.inspect})
70
- puts %(login_was = #{login_was.inspect})
116
+ def settings
117
+ attributes.merge(Vidibus::Pureftpd.settings)
118
+ end
119
+
120
+ def unique_login?
121
+ return unless login.present?
122
+ if User.find_by_login(login)
123
+ self.errors.add(:login, :taken)
124
+ end
125
+ end
126
+
127
+ def valid_directory?
128
+ return unless directory.present?
129
+ if !File.exist?(directory)
130
+ self.errors.add(:directory, :not_existent)
131
+ elsif !File.directory?(directory)
132
+ self.errors.add(:directory, :not_a_directory)
133
+ elsif !File.readable?(directory)
134
+ self.errors.add(:directory, :not_readable)
135
+ elsif !File.writable?(directory)
136
+ self.errors.add(:directory, :not_writable)
137
+ end
71
138
  end
72
139
 
73
140
  def update
74
141
  if login_changed?
75
142
  a = attributes
143
+ destroy
144
+ User.create(a)
76
145
  end
77
146
  if password_changed?
78
147
  update_password
79
148
  end
80
-
81
- # usermod
149
+ if changes.except(:login, :password).any?
150
+ update_attributes
151
+ end
82
152
  end
83
153
 
84
154
  def create
@@ -87,14 +157,22 @@ module Vidibus
87
157
  end
88
158
 
89
159
  def update_password
90
- cmd = 'pure-pw passwd %{login} -f %{password_file} -m' % settings
160
+ cmd = 'passwd %{login} -f %{password_file} -m' % settings
91
161
  perform_with_password_input(cmd)
92
162
  end
93
163
 
164
+ def update_attributes
165
+ cmd = 'usermod %{login} -d %{directory} -f %{password_file} -m' % settings
166
+ perform(cmd)
167
+ end
168
+
169
+ # TODO: Dry this up. But args << &Proc.new isn't possible.
94
170
  def perform(cmd)
95
- Vidibus::Pureftpd.perform(cmd)
96
- rescue Vidibus::Pureftpd::Error => e
97
- raise fucked
171
+ if block_given?
172
+ Vidibus::Pureftpd.perform(cmd, &Proc.new)
173
+ else
174
+ Vidibus::Pureftpd.perform(cmd)
175
+ end
98
176
  end
99
177
 
100
178
  def perform_with_password_input(cmd)
@@ -103,7 +181,6 @@ module Vidibus
103
181
  stdin.puts(password)
104
182
  end
105
183
  end
106
-
107
184
  end
108
185
  end
109
186
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vidibus-pureftpd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,6 +11,22 @@ bindir: bin
11
11
  cert_chain: []
12
12
  date: 2012-09-19 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: posix-spawn
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: vidibus-core_extensions
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -27,6 +43,22 @@ dependencies:
27
43
  - - ! '>='
28
44
  - !ruby/object:Gem::Version
29
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: activemodel
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
30
62
  - !ruby/object:Gem::Dependency
31
63
  name: bundler
32
64
  requirement: !ruby/object:Gem::Requirement
@@ -133,6 +165,8 @@ files:
133
165
  - lib/vidibus/pureftpd/version.rb
134
166
  - lib/vidibus/pureftpd.rb
135
167
  - lib/vidibus-pureftpd.rb
168
+ - config/locales/de.yml
169
+ - config/locales/en.yml
136
170
  - LICENSE
137
171
  - README.md
138
172
  - Rakefile
@@ -150,7 +184,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
184
  version: '0'
151
185
  segments:
152
186
  - 0
153
- hash: -1082857674235287288
187
+ hash: 848917452145310950
154
188
  required_rubygems_version: !ruby/object:Gem::Requirement
155
189
  none: false
156
190
  requirements: