Merge branch 'release-v2.5.0' into stable

This commit is contained in:
Holger Just 2011-11-30 20:44:39 +01:00
commit a4f117fe60
46 changed files with 276 additions and 214764 deletions

View File

@ -18,6 +18,10 @@ group :test do
platforms :mri_19, :mingw_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
end
group :ldap do
gem "net-ldap", '~> 0.2.2'
end
group :openid do
gem "ruby-openid", '~> 2.1.4', :require => 'openid'
end

View File

@ -16,8 +16,8 @@ class UsersController < ApplicationController
layout 'admin'
before_filter :require_admin, :except => :show
before_filter :find_user, :only => [:show, :edit, :update, :edit_membership, :destroy_membership]
accept_key_auth :index, :show, :create, :update
before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
accept_key_auth :index, :show, :create, :update, :destroy
include SortHelper
include CustomFieldsHelper
@ -178,6 +178,24 @@ class UsersController < ApplicationController
redirect_to :controller => 'users', :action => 'edit', :id => @user
end
verify :method => :delete, :only => :destroy, :render => {:nothing => true, :status => :method_not_allowed }
def destroy
# Only allow to delete users with STATUS_REGISTERED for now
# It is assumed that these users are not yet references in any way
# from other objects.
return render_403 unless @user.deletable?
@user.destroy
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_delete)
redirect_back_or_default(:action => 'index')
}
format.api { head :ok }
end
end
def edit_membership
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
@membership.save if request.post?

View File

@ -413,7 +413,7 @@ module ApplicationHelper
title = []
title << h(@project.name) if @project
title += @html_title if @html_title
title << Setting.app_title
title << h(Setting.app_title)
title.select {|t| !t.blank? }.join(' - ')
else
@html_title ||= []

View File

@ -37,13 +37,26 @@ module UsersHelper
def change_status_link(user)
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
links = []
if user.locked?
link_to l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
links << link_to(l(:button_unlock), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock')
elsif user.registered?
link_to l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock'
links << link_to(l(:button_activate), url.merge(:user => {:status => User::STATUS_ACTIVE}), :method => :put, :class => 'icon icon-unlock')
elsif user != User.current
link_to l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock'
links << link_to(l(:button_lock), url.merge(:user => {:status => User::STATUS_LOCKED}), :method => :put, :class => 'icon icon-lock')
end
if user.deletable?
links << link_to(
l(:button_delete), {:controller => 'users', :action => 'destroy', :id => user},
:method => :delete,
:confirm => l(:text_are_you_sure),
:title => l(:button_delete),
:class => 'icon icon-del'
)
end
links.join(" ")
end
def user_settings_tabs

View File

@ -108,7 +108,7 @@ class Journal < ActiveRecord::Base
## => Try the journaled object with the same method and arguments
## => On error, call super
def method_missing(method, *args, &block)
return super if attributes[method.to_s]
return super if respond_to?(method) || attributes[method.to_s]
journaled.send(method, *args, &block)
rescue NoMethodError => e
e.name == method ? super : raise(e)

View File

@ -396,8 +396,8 @@ class Mailer < ActionMailer::Base
# if he doesn't want to receive notifications about what he does
@author ||= User.current
if @author.pref[:no_self_notified]
recipients.delete(@author.mail) if recipients
cc.delete(@author.mail) if cc
recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present?
cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present?
end
notified_users = [recipients, cc].flatten.compact.uniq

View File

@ -70,6 +70,7 @@ class User < Principal
validates_length_of :mail, :maximum => 60, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
validates_inclusion_of :status, :in => [STATUS_ANONYMOUS, STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
named_scope :in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
@ -207,6 +208,11 @@ class User < Principal
update_attribute(:status, STATUS_LOCKED)
end
def deletable?
registered? && last_login_on.nil?
end
# Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password)
if auth_source_id.present?
@ -526,6 +532,24 @@ class User < Principal
if !password.nil? && password.size < Setting.password_min_length.to_i
errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
end
# Status
if !new_record? && status_changed?
case status_was
when nil
# initial setting is always save
true
when STATUS_ANONYMOUS
# never allow a state change of the anonymous user
false
when STATUS_REGISTERED
[STATUS_ACTIVE, STATUS_LOCKED].include? status
when STATUS_ACTIVE
[STATUS_LOCKED].include? status
when STATUS_LOCKED
[STATUS_ACTIVE].include? status
end || errors.add(:status, :inclusion)
end
end
private

View File

@ -104,7 +104,12 @@ class WikiContent < ActiveRecord::Base
def text
@text ||= case changes["compression"]
when "gzip"
Zlib::Inflate.inflate(changes["data"])
data = Zlib::Inflate.inflate(changes["data"])
if data.respond_to? :force_encoding
data.force_encoding("UTF-8")
else
data
end
else
# uncompressed data
changes["data"]

View File

@ -15,7 +15,7 @@
<%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
</fieldset>
<% end %>
<% if authorize_for('timelog', 'edit') %>
<% if User.current.allowed_to?(:log_time, @project) %>
<fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
<div class="splitcontentleft">
@ -26,7 +26,7 @@
</div>
<p><%= time_entry.text_field :comments, :size => 60 %></p>
<% @time_entry.custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<p><%= custom_field_tag_with_label :time_entry, value %></p>
<% end %>
<% end %>
</fieldset>

View File

@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title><%=h html_title %></title>
<title><%= html_title %></title>
<meta name="description" content="<%= Redmine::Info.app_name %>" />
<meta name="keywords" content="issue,bug,tracker" />
<%= csrf_meta_tag %>

View File

@ -8,10 +8,13 @@
<%= link_to(l(:label_overall_spent_time), { :controller => 'time_entries' }) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %>
<%= link_to(l(:label_news_view_all), { :controller => 'news' }) + ' |' if User.current.allowed_to?(:view_news, nil, :global => true) %>
<%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%>
<%= call_hook(:view_projects_show_contextual) %>
</div>
<h2><%=l(:label_project_plural)%></h2>
<%= call_hook(:view_projects_show_top) %>
<%= render_project_hierarchy(@projects)%>
<% if User.current.logged? %>

View File

@ -97,11 +97,11 @@ Event.observe(document,"dom:loaded", apply_filters_observer);
</select>
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
<% when :date, :date_past %>
<%= text_field_tag "v[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
<%= text_field_tag "v[#{field}][]", query.values_for(field).try(:first), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
<% when :string, :text %>
<%= text_field_tag "v[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
<%= text_field_tag "v[#{field}][]", query.values_for(field).try(:first), :id => "values_#{field}", :size => 30, :class => "select-small" %>
<% when :integer %>
<%= text_field_tag "v[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %>
<%= text_field_tag "v[#{field}][]", query.values_for(field).try(:first), :id => "values_#{field}", :size => 3, :class => "select-small" %>
<% end %>
</div>
<script type="text/javascript">toggle_filter('<%= field %>');</script>

View File

@ -960,26 +960,26 @@ bg:
field_effective_date: Дата
text_default_encoding: "По подразбиране: UTF-8"
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
label_notify_member_plural: Email issue updates
label_notify_member_plural: Изпращане на e-mail при промени в задачите
label_path_encoding: Кодиране на пътищата
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
label_diff: diff
setting_issue_startdate_is_adddate: Use current date as start date for new issues
description_search: Searchfield
description_user_mail_notification: Mail notification settings
description_date_range_list: Choose range from list
description_date_to: Enter end date
description_query_sort_criteria_attribute: Sort attribute
description_message_content: Message content
description_wiki_subpages_reassign: Choose new parent page
description_available_columns: Available Columns
description_selected_columns: Selected Columns
description_date_range_interval: Choose range by selecting start and end date
description_project_scope: Search scope
description_issue_category_reassign: Choose issue category
description_query_sort_criteria_direction: Sort direction
description_notes: Notes
description_filter: Filter
description_choose_project: Projects
description_date_from: Enter start date
label_deleted_custom_field: (deleted custom field)
setting_issue_startdate_is_adddate: Използване на текущата дата като начална дата за нови задачи
description_search: Търсене
description_user_mail_notification: Конфигурация известията по пощата
description_date_range_list: Изберете диапазон от списъка
description_date_to: Въведете крайна дата
description_query_sort_criteria_attribute: Атрибут на сортиране
description_message_content: Съдържание на съобщението
description_wiki_subpages_reassign: Изберете нова родителска страница
description_available_columns: Налични колони
description_selected_columns: Избрани колони
description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати
description_project_scope: Обхват на търсенето
description_issue_category_reassign: Изберете категория
description_query_sort_criteria_direction: Посока на сортиране
description_notes: Бележки
description_filter: Филтър
description_choose_project: Проекти
description_date_from: Въведете начална дата
label_deleted_custom_field: (изтрито потребителско поле)

View File

@ -19,9 +19,9 @@ rescue LoadError
raise "Could not load the bundler gem. Install it with `gem install bundler`."
end
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
"Run `gem install bundler` to upgrade."
if Gem::Version.new(Bundler::VERSION) < Gem::Version.new("1.0.6")
raise RuntimeError, "Your bundler version is too old. We require " +
"at least version 1.0.6. Run `gem install bundler` to upgrade."
end
begin
@ -29,6 +29,6 @@ begin
ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
Bundler.setup
rescue Bundler::GemNotFound
raise RuntimeError, "Bundler couldn't find some gems." +
raise RuntimeError, "Bundler couldn't find some gems. " +
"Did you run `bundle install`?"
end

View File

@ -137,8 +137,7 @@ ActionController::Routing::Routes.draw do |map|
map.resources :users, :member => {
:edit_membership => :post,
:destroy_membership => :post
},
:except => [:destroy]
}
# For nice "roadmap" in the url for the index action
map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'

File diff suppressed because it is too large Load Diff

View File

@ -49,7 +49,7 @@ Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
PerlAccessHandler Apache::Authn::Redmine::access_handler
PerlAuthenHandler Apache::Authn::Redmine::authen_handler
## for mysql
RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
## for postgres
@ -227,31 +227,31 @@ my @directives = (
},
);
sub RedmineDSN {
sub RedmineDSN {
my ($self, $parms, $arg) = @_;
$self->{RedmineDSN} = $arg;
my $query = "SELECT
my $query = "SELECT
hashed_password, salt, auth_source_id, permissions
FROM members, projects, users, roles, member_roles
WHERE
WHERE
projects.id=members.project_id
AND member_roles.member_id=members.id
AND users.id=members.user_id
AND users.id=members.user_id
AND roles.id=member_roles.role_id
AND users.status=1
AND login=?
AND users.status=1
AND login=?
AND identifier=? ";
$self->{RedmineQuery} = trim($query);
}
sub RedmineDbUser { set_val('RedmineDbUser', @_); }
sub RedmineDbPass { set_val('RedmineDbPass', @_); }
sub RedmineDbWhereClause {
sub RedmineDbWhereClause {
my ($self, $parms, $arg) = @_;
$self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
}
sub RedmineCacheCredsMax {
sub RedmineCacheCredsMax {
my ($self, $parms, $arg) = @_;
if ($arg) {
$self->{RedmineCachePool} = APR::Pool->new;
@ -325,10 +325,10 @@ sub access_handler {
sub authen_handler {
my $r = shift;
my ($res, $redmine_pass) = $r->get_basic_auth_pw();
return $res unless $res == OK;
if (is_member($r->user, $redmine_pass, $r)) {
return OK;
} else {
@ -355,7 +355,7 @@ sub is_authentication_forced {
}
$sth->finish();
undef $sth;
$dbh->disconnect();
undef $dbh;
@ -365,7 +365,7 @@ sub is_authentication_forced {
sub is_public_project {
my $project_id = shift;
my $r = shift;
if (is_authentication_forced($r)) {
return 0;
}
@ -392,12 +392,12 @@ sub is_public_project {
sub anonymous_role_allows_browse_repository {
my $r = shift;
my $dbh = connect_database($r);
my $sth = $dbh->prepare(
"SELECT permissions FROM roles WHERE builtin = 2;"
);
$sth->execute();
my $ret = 0;
if (my @row = $sth->fetchrow_array) {
@ -409,7 +409,7 @@ sub anonymous_role_allows_browse_repository {
undef $sth;
$dbh->disconnect();
undef $dbh;
$ret;
}
@ -438,10 +438,12 @@ sub is_member {
my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
my $access_mode = request_is_read_only($r) ? "R" : "W";
my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
my $usrprojpass;
if ($cfg->{RedmineCacheCredsMax}) {
$usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
$usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id.":".$access_mode);
return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
}
my $query = $cfg->{RedmineQuery};
@ -485,10 +487,10 @@ sub is_member {
if ($cfg->{RedmineCacheCredsMax} and $ret) {
if (defined $usrprojpass) {
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
} else {
if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
$cfg->{RedmineCacheCredsCount}++;
} else {
$cfg->{RedmineCacheCreds}->clear();
@ -502,7 +504,7 @@ sub is_member {
sub get_project_identifier {
my $r = shift;
my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
my $location = $r->location;
my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
@ -512,7 +514,7 @@ sub get_project_identifier {
sub connect_database {
my $r = shift;
my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
}

View File

@ -21,5 +21,19 @@ module ChiliProject
Journal.included_modules.include?(Redmine::Acts::Journalized)
end
# Is any jQuery version available on all pages?
#
# This does not take modifications into account, that may be performed by
# plugins.
#
# Released: ChiliProject 2.5.0
def self.using_jquery?
false
end
# Catch-all to be overwritten be future compatibility checks.
def self.method_missing(method, *args)
method.to_s.ends_with?('?') ? false : super
end
end
end

View File

@ -18,7 +18,7 @@ module ChiliProject
module VERSION #:nodoc:
MAJOR = 2
MINOR = 4
MINOR = 5
PATCH = 0
TINY = PATCH # Redmine compat

View File

@ -100,10 +100,10 @@ Redmine::AccessControl.map do |map|
end
map.project_module :time_tracking do |map|
map.permission :log_time, {:timelog => [:new, :create, :edit, :update]}, :require => :loggedin
map.permission :log_time, {:timelog => [:new, :create]}, :require => :loggedin
map.permission :view_time_entries, :timelog => [:index, :show], :time_entry_reports => [:report]
map.permission :edit_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :member
map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
map.permission :edit_time_entries, {:timelog => [:edit, :update, :destroy]}, :require => :member
map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy]}, :require => :loggedin
map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
end

View File

@ -22,7 +22,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
super
end
(field_helpers - %w(radio_button hidden_field fields_for) + %w(date_select)).each do |selector|
(field_helpers.map(&:to_s) - %w(radio_button hidden_field fields_for) + %w(date_select)).each do |selector|
src = <<-END_SRC
def #{selector}(field, options = {})
label_for_field(field, options) + super

View File

@ -781,6 +781,22 @@ class IssuesControllerTest < ActionController::TestCase
assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
end
def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
@request.session[:user_id] = 2
Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
get :edit, :id => 1
assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
end
def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
@request.session[:user_id] = 2
Role.find_by_name('Manager').remove_permission! :log_time
get :edit, :id => 1
assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
end
def test_update_edit_form
@request.session[:user_id] = 2
xhr :post, :new, :project_id => 1,

View File

@ -111,6 +111,18 @@ class TimelogControllerTest < ActionController::TestCase
assert_equal 3, t.user_id
end
def test_create_without_log_time_permission_should_be_denied
@request.session[:user_id] = 2
Role.find_by_name('Manager').remove_permission! :log_time
post :create, :project_id => 1,
:time_entry => {:activity_id => '11',
:issue_id => '',
:spent_on => '2008-03-14',
:hours => '7.3'}
assert_response 403
end
def test_update
entry = TimeEntry.find(1)
assert_equal 1, entry.issue_id

View File

@ -270,6 +270,31 @@ class UsersControllerTest < ActionController::TestCase
assert u.check_password?('newpass')
end
def test_destroy
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
u.login = 'death.row'
u.status = User::STATUS_REGISTERED
u.save!
delete :destroy, :id => u.id
assert_redirected_to :action => 'index'
# make sure that the user was actually destroyed
assert_raises(ActiveRecord::RecordNotFound) { u.reload }
end
def test_failing_destroy
u = User.new(:firstname => 'Surviving', :lastname => 'Patient', :mail => 'surviving.patient@example.com', :language => 'en')
u.login = 'surviving.patient'
u.status = User::STATUS_ACTIVE
u.save!
delete :destroy, :id => u.id
assert_response :forbidden
# make sure the user is still around
assert !u.reload.destroyed?
end
def test_edit_membership
post :edit_membership, :id => 2, :membership_id => 1,
:membership => { :role_ids => [2]}

View File

@ -241,26 +241,52 @@ class ApiTest::UsersTest < ActionController::IntegrationTest
end
end
end
end
context "DELETE /users/2" do
context ".xml" do
should "not be allowed" do
assert_no_difference('User.count') do
delete '/users/2.xml'
end
context "DELETE /users/:temp:" do
context ".xml" do
should "delete the user" do
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
u.login = 'death.row'
u.status = User::STATUS_REGISTERED
u.save!
assert_response :method_not_allowed
assert_difference('User.count',-1) do
delete "/users/#{u.id}.xml", {}, :authorization => credentials('admin')
end
assert_response :success
assert_nil User.find_by_id(u.id)
end
context ".json" do
should "not be allowed" do
assert_no_difference('User.count') do
delete '/users/2.json'
end
assert_response :method_not_allowed
should "not delete active user" do
assert_difference('User.count',0) do
delete "/users/2.xml", {}, :authorization => credentials('jsmith')
end
assert_response :forbidden
end
end
context ".json" do
should "delete the user" do
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
u.login = 'death.row'
u.status = User::STATUS_REGISTERED
u.save!
assert_difference('User.count',-1) do
delete "/users/#{u.id}.json", {}, :authorization => credentials('admin')
end
assert_response :success
assert_nil User.find_by_id(u.id)
end
should "not delete active user" do
assert_difference('User.count',0) do
delete "/users/2.json", {}, :authorization => credentials('jsmith')
end
assert_response :forbidden
end
end
end

View File

@ -60,4 +60,15 @@ class LayoutTest < ActionController::IntegrationTest
:attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}},
:parent => {:tag => 'head'}
end
test "page titles should be properly escaped" do
project = Project.generate(:name => "C&A")
with_settings :app_title => '<3' do
get "/projects/#{project.to_param}"
assert_select "title", /C&amp;A/
assert_select "title", /&lt;3/
end
end
end

View File

@ -174,6 +174,14 @@ class UserTest < ActiveSupport::TestCase
assert_equal nil, user
end
def test_error_on_active_to_registered
user = User.try_to_login("jsmith", "jsmith")
assert_equal @jsmith, user
@jsmith.status = User::STATUS_REGISTERED
assert !@jsmith.save
end
context ".try_to_login" do
context "with good credentials" do
should "return the user" do

View File

@ -1,272 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street,
Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and
distribute verbatim copies of this license document, but changing it is not
allowed.
Preamble
The licenses for most software are designed to take away your freedom to
share and change it. By contrast, the GNU General Public License is
intended to guarantee your freedom to share and change free software--to
make sure the software is free for all its users. This General Public
License applies to most of the Free Software Foundation's software and to
any other program whose authors commit to using it. (Some other Free
Software Foundation software is covered by the GNU Lesser General Public
License instead.) You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish), that you receive source code or can get it if you want it, that you
can change the software or use pieces of it in new free programs; and that
you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to
deny you these rights or to ask you to surrender the rights. These
restrictions translate to certain responsibilities for you if you distribute
copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or
for a fee, you must give the recipients all the rights that you have. You
must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2)
offer you this license which gives you legal permission to copy, distribute
and/or modify the software.
Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If
the software is modified by someone else and passed on, we want its
recipients to know that what they have is not the original, so that any
problems introduced by others will not reflect on the original authors'
reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that redistributors of a free program will
individually obtain patent licenses, in effect making the program
proprietary. To prevent this, we have made it clear that any patent must be
licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification
follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the
terms of this General Public License. The "Program", below, refers to
any such program or work, and a "work based on the Program" means either
the Program or any derivative work under copyright law: that is to say, a
work containing the Program or a portion of it, either verbatim or with
modifications and/or translated into another language. (Hereinafter,
translation is included without limitation in the term "modification".)
Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of running
the Program is not restricted, and the output from the Program is covered
only if its contents constitute a work based on the Program (independent
of having been made by running the Program). Whether that is true depends
on what the Program does.
1. You may copy and distribute verbatim copies of the Program's source code
as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and
disclaimer of warranty; keep intact all the notices that refer to this
License and to the absence of any warranty; and give any other recipients
of the Program a copy of this License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it,
thus forming a work based on the Program, and copy and distribute such
modifications or work under the terms of Section 1 above, provided that
you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices stating
that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole
or in part contains or is derived from the Program or any part
thereof, to be licensed as a whole at no charge to all third parties
under the terms of this License.
c) If the modified program normally reads commands interactively when
run, you must cause it, when started running for such interactive use
in the most ordinary way, to print or display an announcement
including an appropriate copyright notice and a notice that there is
no warranty (or else, saying that you provide a warranty) and that
users may redistribute the program under these conditions, and telling
the user how to view a copy of this License. (Exception: if the
Program itself is interactive but does not normally print such an
announcement, your work based on the Program is not required to print
an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program, and
can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based on
the Program, the distribution of the whole must be on the terms of this
License, whose permissions for other licensees extend to the entire
whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of a
storage or distribution medium does not bring the other work under the
scope of this License.
3. You may copy and distribute the Program (or a work based on it, under
Section 2) in object code or executable form under the terms of Sections
1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source
code, which must be distributed under the terms of Sections 1 and 2
above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to
give any third party, for a charge no more than your cost of
physically performing source distribution, a complete machine-readable
copy of the corresponding source code, to be distributed under the
terms of Sections 1 and 2 above on a medium customarily used for
software interchange; or,
c) Accompany it with the information you received as to the offer to
distribute corresponding source code. (This alternative is allowed
only for noncommercial distribution and only if you received the
program in object code or executable form with such an offer, in
accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source code
means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to control
compilation and installation of the executable. However, as a special
exception, the source code distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on which
the executable runs, unless that component itself accompanies the
executable.
If distribution of executable or object code is made by offering access
to copy from a designated place, then offering equivalent access to copy
the source code from the same place counts as distribution of the source
code, even though third parties are not compelled to copy the source
along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program except as
expressly provided under this License. Any attempt otherwise to copy,
modify, sublicense or distribute the Program is void, and will
automatically terminate your rights under this License. However, parties
who have received copies, or rights, from you under this License will not
have their licenses terminated so long as such parties remain in full
compliance.
5. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute
the Program or its derivative works. These actions are prohibited by law
if you do not accept this License. Therefore, by modifying or
distributing the Program (or any work based on the Program), you indicate
your acceptance of this License to do so, and all its terms and
conditions for copying, distributing or modifying the Program or works
based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further restrictions
on the recipients' exercise of the rights granted herein. You are not
responsible for enforcing compliance by third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot distribute
so as to satisfy simultaneously your obligations under this License and
any other pertinent obligations, then as a consequence you may not
distribute the Program at all. For example, if a patent license would
not permit royalty-free redistribution of the Program by all those who
receive copies directly or indirectly through you, then the only way you
could satisfy both it and this License would be to refrain entirely from
distribution of the Program.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any such
claims; this section has the sole purpose of protecting the integrity of
the free software distribution system, which is implemented by public
license practices. Many people have made generous contributions to the
wide range of software distributed through that system in reliance on
consistent application of that system; it is up to the author/donor to
decide if he or she is willing to distribute software through any other
system and a licensee cannot impose that choice.
This section is intended to make thoroughly clear what is believed to be
a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original
copyright holder who places the Program under this License may add an
explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions of
the General Public License from time to time. Such new versions will be
similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free
Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask
for permission. For software which is copyrighted by the Free Software
Foundation, write to the Free Software Foundation; we sometimes make
exceptions for this. Our decision will be guided by the two goals of
preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

View File

@ -1,58 +0,0 @@
= Net::LDAP Changelog
== Net::LDAP 0.0.4: August 15, 2006
* Undeprecated Net::LDAP#modify. Thanks to Justin Forder for
providing the rationale for this.
* Added a much-expanded set of special characters to the parser
for RFC-2254 filters. Thanks to Andre Nathan.
* Changed Net::LDAP#search so you can pass it a filter in string form.
The conversion to a Net::LDAP::Filter now happens automatically.
* Implemented Net::LDAP#bind_as (preliminary and subject to change).
Thanks for Simon Claret for valuable suggestions and for helping test.
* Fixed bug in Net::LDAP#open that was preventing #open from being
called more than one on a given Net::LDAP object.
== Net::LDAP 0.0.3: July 26, 2006
* Added simple TLS encryption.
Thanks to Garett Shulman for suggestions and for helping test.
== Net::LDAP 0.0.2: July 12, 2006
* Fixed malformation in distro tarball and gem.
* Improved documentation.
* Supported "paged search control."
* Added a range of API improvements.
* Thanks to Andre Nathan, andre@digirati.com.br, for valuable
suggestions.
* Added support for LE and GE search filters.
* Added support for Search referrals.
* Fixed a regression with openldap 2.2.x and higher caused
by the introduction of RFC-2696 controls. Thanks to Andre
Nathan for reporting the problem.
* Added support for RFC-2254 filter syntax.
== Net::LDAP 0.0.1: May 1, 2006
* Initial release.
* Client functionality is near-complete, although the APIs
are not guaranteed and may change depending on feedback
from the community.
* We're internally working on a Ruby-based implementation
of a full-featured, production-quality LDAP server,
which will leverage the underlying LDAP and BER functionality
in Net::LDAP.
* Please tell us if you would be interested in seeing a public
release of the LDAP server.
* Grateful acknowledgement to Austin Ziegler, who reviewed
this code and provided the release framework, including
minitar.
#--
# Net::LDAP for Ruby.
# http://rubyforge.org/projects/net-ldap/
# Copyright (C) 2006 by Francis Cianfrocca
#
# Available under the same terms as Ruby. See LICENCE in the main
# distribution for full licensing information.
#
# $Id: ChangeLog,v 1.17.2.4 2005/09/09 12:36:42 austin Exp $
#++
# vim: sts=2 sw=2 ts=4 et ai tw=77

View File

@ -1,55 +0,0 @@
Net::LDAP is copyrighted free software by Francis Cianfrocca
<garbagecat10@gmail.com>. You can redistribute it and/or modify it under either
the terms of the GPL (see the file COPYING), or the conditions below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that you do
at least ONE of the following:
a) place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or
an equivalent medium, or by allowing the author to include your
modifications in the software.
b) use the modified software only within your corporation or
organization.
c) rename any non-standard executables so the names do not conflict with
standard executables, which must also be provided.
d) make other distribution arrangements with the author.
3. You may distribute the software in object code or executable form,
provided that you do at least ONE of the following:
a) distribute the executables and library files of the software, together
with instructions (in the manual page or equivalent) on where to get
the original distribution.
b) accompany the distribution with the machine-readable source of the
software.
c) give non-standard executables non-standard names, with instructions on
where to get the original software distribution.
d) make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution are
not written by the author, so that they are not under this terms.
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
files under the ./missing directory. See each file for the copying
condition.
5. The scripts and library files supplied as input to or produced as output
from the software do not automatically fall under the copyright of the
software, but belong to whomever generated them, and may be sold
commercially, and may be aggregated with this software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

View File

@ -1,32 +0,0 @@
= Net::LDAP for Ruby
Net::LDAP is an LDAP support library written in pure Ruby. It supports all
LDAP client features, and a subset of server features as well.
Homepage:: http://rubyforge.org/projects/net-ldap/
Copyright:: (C) 2006 by Francis Cianfrocca
Original developer: Francis Cianfrocca
Contributions by Austin Ziegler gratefully acknowledged.
== LICENCE NOTES
Please read the file LICENCE for licensing restrictions on this library. In
the simplest terms, this library is available under the same terms as Ruby
itself.
== Requirements
Net::LDAP requires Ruby 1.8.2 or better.
== Documentation
See Net::LDAP for documentation and usage samples.
#--
# Net::LDAP for Ruby.
# http://rubyforge.org/projects/net-ldap/
# Copyright (C) 2006 by Francis Cianfrocca
#
# Available under the same terms as Ruby. See LICENCE in the main
# distribution for full licensing information.
#
# $Id: README 141 2006-07-12 10:37:37Z blackhedd $
#++
# vim: sts=2 sw=2 ts=4 et ai tw=77

View File

@ -1,295 +0,0 @@
#-- encoding: UTF-8
# $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $
#
# NET::BER
# Mixes ASN.1/BER convenience methods into several standard classes.
# Also provides BER parsing functionality.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
module BER
class BerError < Exception; end
# This module is for mixing into IO and IO-like objects.
module BERParser
# The order of these follows the class-codes in BER.
# Maybe this should have been a hash.
TagClasses = [:universal, :application, :context_specific, :private]
BuiltinSyntax = {
:universal => {
:primitive => {
1 => :boolean,
2 => :integer,
4 => :string,
10 => :integer,
},
:constructed => {
16 => :array,
17 => :array
}
}
}
#
# read_ber
# TODO: clean this up so it works properly with partial
# packets coming from streams that don't block when
# we ask for more data (like StringIOs). At it is,
# this can throw TypeErrors and other nasties.
#
def read_ber syntax=nil
return nil if (StringIO == self.class) and eof?
id = getc # don't trash this value, we'll use it later
tag = id & 31
tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
tagclass = TagClasses[ id >> 6 ]
encoding = (id & 0x20 != 0) ? :constructed : :primitive
n = getc
lengthlength,contentlength = if n <= 127
[1,n]
else
j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
[1 + (n & 127), j]
end
newobj = read contentlength
objtype = nil
[syntax, BuiltinSyntax].each {|syn|
if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
objtype = ot[tag]
break
end
}
obj = case objtype
when :boolean
newobj != "\000"
when :string
(newobj || "").dup
when :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
when :array
seq = []
sio = StringIO.new( newobj || "" )
# Interpret the subobject, but note how the loop
# is built: nil ends the loop, but false (a valid
# BER value) does not!
while (e = sio.read_ber(syntax)) != nil
seq << e
end
seq
else
raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
end
# Add the identifier bits into the object if it's a String or an Array.
# We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
obj
end
end # module BERParser
end # module BER
end # module Net
class IO
include Net::BER::BERParser
end
require "stringio"
class StringIO
include Net::BER::BERParser
end
begin
require 'openssl'
class OpenSSL::SSL::SSLSocket
include Net::BER::BERParser
end
rescue LoadError
# Ignore LoadError.
# DON'T ignore NameError, which means the SSLSocket class
# is somehow unavailable on this implementation of Ruby's openssl.
# This may be WRONG, however, because we don't yet know how Ruby's
# openssl behaves on machines with no OpenSSL library. I suppose
# it's possible they do not fail to require 'openssl' but do not
# create the classes. So this code is provisional.
# Also, you might think that OpenSSL::SSL::SSLSocket inherits from
# IO so we'd pick it up above. But you'd be wrong.
end
class String
def read_ber syntax=nil
StringIO.new(self).read_ber(syntax)
end
end
#----------------------------------------------
class FalseClass
#
# to_ber
#
def to_ber
"\001\001\000"
end
end
class TrueClass
#
# to_ber
#
def to_ber
"\001\001\001"
end
end
class Fixnum
#
# to_ber
#
def to_ber
i = [self].pack('w')
[2, i.length].pack("CC") + i
end
#
# to_ber_enumerated
#
def to_ber_enumerated
i = [self].pack('w')
[10, i.length].pack("CC") + i
end
#
# to_ber_length_encoding
#
def to_ber_length_encoding
if self <= 127
[self].pack('C')
else
i = [self].pack('N').sub(/^[\0]+/,"")
[0x80 + i.length].pack('C') + i
end
end
end # class Fixnum
class Bignum
def to_ber
i = [self].pack('w')
i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
[2, i.length].pack("CC") + i
end
end
class String
#
# to_ber
# A universal octet-string is tag number 4,
# but others are possible depending on the context, so we
# let the caller give us one.
# The preferred way to do this in user code is via to_ber_application_sring
# and to_ber_contextspecific.
#
def to_ber code = 4
[code].pack('C') + length.to_ber_length_encoding + self
end
#
# to_ber_application_string
#
def to_ber_application_string code
to_ber( 0x40 + code )
end
#
# to_ber_contextspecific
#
def to_ber_contextspecific code
to_ber( 0x80 + code )
end
end # class String
class Array
#
# to_ber_appsequence
# An application-specific sequence usually gets assigned
# a tag that is meaningful to the particular protocol being used.
# This is different from the universal sequence, which usually
# gets a tag value of 16.
# Now here's an interesting thing: We're adding the X.690
# "application constructed" code at the top of the tag byte (0x60),
# but some clients, notably ldapsearch, send "context-specific
# constructed" (0xA0). The latter would appear to violate RFC-1777,
# but what do I know? We may need to change this.
#
def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
private
def to_ber_seq_internal code
s = self.to_s
[code].pack('C') + s.length.to_ber_length_encoding + s
end
end # class Array

File diff suppressed because it is too large Load Diff

View File

@ -1,109 +0,0 @@
#-- encoding: UTF-8
# $Id: dataset.rb 78 2006-04-26 02:57:34Z blackhedd $
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
class LDAP
class Dataset < Hash
attr_reader :comments
def Dataset::read_ldif io
ds = Dataset.new
line = io.gets && chomp
dn = nil
while line
io.gets and chomp
if $_ =~ /^[\s]+/
line << " " << $'
else
nextline = $_
if line =~ /^\#/
ds.comments << line
elsif line =~ /^dn:[\s]*/i
dn = $'
ds[dn] = Hash.new {|k,v| k[v] = []}
elsif line.length == 0
dn = nil
elsif line =~ /^([^:]+):([\:]?)[\s]*/
# $1 is the attribute name
# $2 is a colon iff the attr-value is base-64 encoded
# $' is the attr-value
# Avoid the Base64 class because not all Ruby versions have it.
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
ds[dn][$1.downcase.intern] << attrvalue
end
line = nextline
end
end
ds
end
def initialize
@comments = []
end
def to_ldif
ary = []
ary += (@comments || [])
keys.sort.each {|dn|
ary << "dn: #{dn}"
self[dn].keys.map {|sym| sym.to_s}.sort.each {|attr|
self[dn][attr.intern].each {|val|
ary << "#{attr}: #{val}"
}
}
ary << ""
}
block_given? and ary.each {|line| yield line}
ary
end
end # Dataset
end # LDAP
end # Net

View File

@ -1,166 +0,0 @@
#-- encoding: UTF-8
# $Id: entry.rb 123 2006-05-18 03:52:38Z blackhedd $
#
# LDAP Entry (search-result) support classes
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
module Net
class LDAP
# Objects of this class represent individual entries in an LDAP
# directory. User code generally does not instantiate this class.
# Net::LDAP#search provides objects of this class to user code,
# either as block parameters or as return values.
#
# In LDAP-land, an "entry" is a collection of attributes that are
# uniquely and globally identified by a DN ("Distinguished Name").
# Attributes are identified by short, descriptive words or phrases.
# Although a directory is
# free to implement any attribute name, most of them follow rigorous
# standards so that the range of commonly-encountered attribute
# names is not large.
#
# An attribute name is case-insensitive. Most directories also
# restrict the range of characters allowed in attribute names.
# To simplify handling attribute names, Net::LDAP::Entry
# internally converts them to a standard format. Therefore, the
# methods which take attribute names can take Strings or Symbols,
# and work correctly regardless of case or capitalization.
#
# An attribute consists of zero or more data items called
# <i>values.</i> An entry is the combination of a unique DN, a set of attribute
# names, and a (possibly-empty) array of values for each attribute.
#
# Class Net::LDAP::Entry provides convenience methods for dealing
# with LDAP entries.
# In addition to the methods documented below, you may access individual
# attributes of an entry simply by giving the attribute name as
# the name of a method call. For example:
# ldap.search( ... ) do |entry|
# puts "Common name: #{entry.cn}"
# puts "Email addresses:"
# entry.mail.each {|ma| puts ma}
# end
# If you use this technique to access an attribute that is not present
# in a particular Entry object, a NoMethodError exception will be raised.
#
#--
# Ugly problem to fix someday: We key off the internal hash with
# a canonical form of the attribute name: convert to a string,
# downcase, then take the symbol. Unfortunately we do this in
# at least three places. Should do it in ONE place.
class Entry
# This constructor is not generally called by user code.
def initialize dn = nil # :nodoc:
@myhash = Hash.new {|k,v| k[v] = [] }
@myhash[:dn] = [dn]
end
def []= name, value # :nodoc:
sym = name.to_s.downcase.intern
@myhash[sym] = value
end
#--
# We have to deal with this one as we do with []=
# because this one and not the other one gets called
# in formulations like entry["CN"] << cn.
#
def [] name # :nodoc:
name = name.to_s.downcase.intern unless name.is_a?(Symbol)
@myhash[name]
end
# Returns the dn of the Entry as a String.
def dn
self[:dn][0]
end
# Returns an array of the attribute names present in the Entry.
def attribute_names
@myhash.keys
end
# Accesses each of the attributes present in the Entry.
# Calls a user-supplied block with each attribute in turn,
# passing two arguments to the block: a Symbol giving
# the name of the attribute, and a (possibly empty)
# Array of data values.
#
def each
if block_given?
attribute_names.each {|a|
attr_name,values = a,self[a]
yield attr_name, values
}
end
end
alias_method :each_attribute, :each
#--
# Convenience method to convert unknown method names
# to attribute references. Of course the method name
# comes to us as a symbol, so let's save a little time
# and not bother with the to_s.downcase two-step.
# Of course that means that a method name like mAIL
# won't work, but we shouldn't be encouraging that
# kind of bad behavior in the first place.
# Maybe we should thow something if the caller sends
# arguments or a block...
#
def method_missing *args, &block # :nodoc:
s = args[0].to_s.downcase.intern
if attribute_names.include?(s)
self[s]
elsif s.to_s[-1] == 61 and s.to_s.length > 1
value = args[1] or raise RuntimeError.new( "unable to set value" )
value = [value] unless value.is_a?(Array)
name = s.to_s[0..-2].intern
self[name] = value
else
raise NoMethodError.new( "undefined method '#{s}'" )
end
end
def write
end
end # class Entry
end # class LDAP
end # module Net

View File

@ -1,388 +0,0 @@
#-- encoding: UTF-8
# $Id: filter.rb 151 2006-08-15 08:34:53Z blackhedd $
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
class LDAP
# Class Net::LDAP::Filter is used to constrain
# LDAP searches. An object of this class is
# passed to Net::LDAP#search in the parameter :filter.
#
# Net::LDAP::Filter supports the complete set of search filters
# available in LDAP, including conjunction, disjunction and negation
# (AND, OR, and NOT). This class supplants the (infamous) RFC-2254
# standard notation for specifying LDAP search filters.
#
# Here's how to code the familiar "objectclass is present" filter:
# f = Net::LDAP::Filter.pres( "objectclass" )
# The object returned by this code can be passed directly to
# the <tt>:filter</tt> parameter of Net::LDAP#search.
#
# See the individual class and instance methods below for more examples.
#
class Filter
def initialize op, a, b
@op = op
@left = a
@right = b
end
# #eq creates a filter object indicating that the value of
# a paticular attribute must be either <i>present</i> or must
# match a particular string.
#
# To specify that an attribute is "present" means that only
# directory entries which contain a value for the particular
# attribute will be selected by the filter. This is useful
# in case of optional attributes such as <tt>mail.</tt>
# Presence is indicated by giving the value "*" in the second
# parameter to #eq. This example selects only entries that have
# one or more values for <tt>sAMAccountName:</tt>
# f = Net::LDAP::Filter.eq( "sAMAccountName", "*" )
#
# To match a particular range of values, pass a string as the
# second parameter to #eq. The string may contain one or more
# "*" characters as wildcards: these match zero or more occurrences
# of any character. Full regular-expressions are <i>not</i> supported
# due to limitations in the underlying LDAP protocol.
# This example selects any entry with a <tt>mail</tt> value containing
# the substring "anderson":
# f = Net::LDAP::Filter.eq( "mail", "*anderson*" )
#--
# Removed gt and lt. They ain't in the standard!
#
def Filter::eq attribute, value; Filter.new :eq, attribute, value; end
def Filter::ne attribute, value; Filter.new :ne, attribute, value; end
#def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
#def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
def Filter::ge attribute, value; Filter.new :ge, attribute, value; end
def Filter::le attribute, value; Filter.new :le, attribute, value; end
# #pres( attribute ) is a synonym for #eq( attribute, "*" )
#
def Filter::pres attribute; Filter.eq attribute, "*"; end
# operator & ("AND") is used to conjoin two or more filters.
# This expression will select only entries that have an <tt>objectclass</tt>
# attribute AND have a <tt>mail</tt> attribute that begins with "George":
# f = Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.eq( "mail", "George*" )
#
def & filter; Filter.new :and, self, filter; end
# operator | ("OR") is used to disjoin two or more filters.
# This expression will select entries that have either an <tt>objectclass</tt>
# attribute OR a <tt>mail</tt> attribute that begins with "George":
# f = Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.eq( "mail", "George*" )
#
def | filter; Filter.new :or, self, filter; end
#
# operator ~ ("NOT") is used to negate a filter.
# This expression will select only entries that <i>do not</i> have an <tt>objectclass</tt>
# attribute:
# f = ~ Net::LDAP::Filter.pres( "objectclass" )
#
#--
# This operator can't be !, evidently. Try it.
# Removed GT and LT. They're not in the RFC.
def ~@; Filter.new :not, self, nil; end
def to_s
case @op
when :ne
"(!(#{@left}=#{@right}))"
when :eq
"(#{@left}=#{@right})"
#when :gt
# "#{@left}>#{@right}"
#when :lt
# "#{@left}<#{@right}"
when :ge
"#{@left}>=#{@right}"
when :le
"#{@left}<=#{@right}"
when :and
"(&(#{@left})(#{@right}))"
when :or
"(|(#{@left})(#{@right}))"
when :not
"(!(#{@left}))"
else
raise "invalid or unsupported operator in LDAP Filter"
end
end
#--
# to_ber
# Filter ::=
# CHOICE {
# and [0] SET OF Filter,
# or [1] SET OF Filter,
# not [2] Filter,
# equalityMatch [3] AttributeValueAssertion,
# substrings [4] SubstringFilter,
# greaterOrEqual [5] AttributeValueAssertion,
# lessOrEqual [6] AttributeValueAssertion,
# present [7] AttributeType,
# approxMatch [8] AttributeValueAssertion
# }
#
# SubstringFilter
# SEQUENCE {
# type AttributeType,
# SEQUENCE OF CHOICE {
# initial [0] LDAPString,
# any [1] LDAPString,
# final [2] LDAPString
# }
# }
#
# Parsing substrings is a little tricky.
# We use the split method to break a string into substrings
# delimited by the * (star) character. But we also need
# to know whether there is a star at the head and tail
# of the string. A Ruby particularity comes into play here:
# if you split on * and the first character of the string is
# a star, then split will return an array whose first element
# is an _empty_ string. But if the _last_ character of the
# string is star, then split will return an array that does
# _not_ add an empty string at the end. So we have to deal
# with all that specifically.
#
def to_ber
case @op
when :eq
if @right == "*" # present
@left.to_s.to_ber_contextspecific 7
elsif @right =~ /[\*]/ #substring
ary = @right.split( /[\*]+/ )
final_star = @right =~ /[\*]$/
initial_star = ary.first == "" and ary.shift
seq = []
unless initial_star
seq << ary.shift.to_ber_contextspecific(0)
end
n_any_strings = ary.length - (final_star ? 0 : 1)
#p n_any_strings
n_any_strings.times {
seq << ary.shift.to_ber_contextspecific(1)
}
unless final_star
seq << ary.shift.to_ber_contextspecific(2)
end
[@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific 4
else #equality
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 3
end
when :ge
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 5
when :le
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 6
when :and
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 )
when :or
ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
ary.map {|a| a.to_ber}.to_ber_contextspecific( 1 )
when :not
[@left.to_ber].to_ber_contextspecific 2
else
# ERROR, we'll return objectclass=* to keep things from blowing up,
# but that ain't a good answer and we need to kick out an error of some kind.
raise "unimplemented search filter"
end
end
#--
# coalesce
# This is a private helper method for dealing with chains of ANDs and ORs
# that are longer than two. If BOTH of our branches are of the specified
# type of joining operator, then return both of them as an array (calling
# coalesce recursively). If they're not, then return an array consisting
# only of self.
#
def coalesce operator
if @op == operator
[@left.coalesce( operator ), @right.coalesce( operator )]
else
[self]
end
end
#--
# We get a Ruby object which comes from parsing an RFC-1777 "Filter"
# object. Convert it to a Net::LDAP::Filter.
# TODO, we're hardcoding the RFC-1777 BER-encodings of the various
# filter types. Could pull them out into a constant.
#
def Filter::parse_ldap_filter obj
case obj.ber_identifier
when 0x87 # present. context-specific primitive 7.
Filter.eq( obj.to_s, "*" )
when 0xa3 # equalityMatch. context-specific constructed 3.
Filter.eq( obj[0], obj[1] )
else
raise LdapError.new( "unknown ldap search-filter type: #{obj.ber_identifier}" )
end
end
#--
# We got a hash of attribute values.
# Do we match the attributes?
# Return T/F, and call match recursively as necessary.
def match entry
case @op
when :eq
if @right == "*"
l = entry[@left] and l.length > 0
else
l = entry[@left] and l = l.to_a and l.index(@right)
end
else
raise LdapError.new( "unknown filter type in match: #{@op}" )
end
end
# Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
# to a Net::LDAP::Filter.
def self.construct ldap_filter_string
FilterParser.new(ldap_filter_string).filter
end
# Synonym for #construct.
# to a Net::LDAP::Filter.
def self.from_rfc2254 ldap_filter_string
construct ldap_filter_string
end
end # class Net::LDAP::Filter
class FilterParser #:nodoc:
attr_reader :filter
def initialize str
require 'strscan'
@filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" )
end
def parse scanner
parse_filter_branch(scanner) or parse_paren_expression(scanner)
end
def parse_paren_expression scanner
if scanner.scan(/\s*\(\s*/)
b = if scanner.scan(/\s*\&\s*/)
a = nil
branches = []
while br = parse_paren_expression(scanner)
branches << br
end
if branches.length >= 2
a = branches.shift
while branches.length > 0
a = a & branches.shift
end
a
end
elsif scanner.scan(/\s*\|\s*/)
# TODO: DRY!
a = nil
branches = []
while br = parse_paren_expression(scanner)
branches << br
end
if branches.length >= 2
a = branches.shift
while branches.length > 0
a = a | branches.shift
end
a
end
elsif scanner.scan(/\s*\!\s*/)
br = parse_paren_expression(scanner)
if br
~ br
end
else
parse_filter_branch( scanner )
end
if b and scanner.scan( /\s*\)\s*/ )
b
end
end
end
# Added a greatly-augmented filter contributed by Andre Nathan
# for detecting special characters in values. (15Aug06)
def parse_filter_branch scanner
scanner.scan(/\s*/)
if token = scanner.scan( /[\w\-_]+/ )
scanner.scan(/\s*/)
if op = scanner.scan( /\=|\<\=|\<|\>\=|\>|\!\=/ )
scanner.scan(/\s*/)
#if value = scanner.scan( /[\w\*\.]+/ ) (ORG)
if value = scanner.scan( /[\w\*\.\+\-@=#\$%&!]+/ )
case op
when "="
Filter.eq( token, value )
when "!="
Filter.ne( token, value )
when "<"
Filter.lt( token, value )
when "<="
Filter.le( token, value )
when ">"
Filter.gt( token, value )
when ">="
Filter.ge( token, value )
end
end
end
end
end
end # class Net::LDAP::FilterParser
end # class Net::LDAP
end # module Net

View File

@ -1,206 +0,0 @@
#-- encoding: UTF-8
# $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $
#
# LDAP PDU support classes
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
module Net
class LdapPduError < Exception; end
class LdapPdu
BindResult = 1
SearchReturnedData = 4
SearchResult = 5
ModifyResponse = 7
AddResponse = 9
DeleteResponse = 11
ModifyRDNResponse = 13
SearchResultReferral = 19
attr_reader :msg_id, :app_tag
attr_reader :search_dn, :search_attributes, :search_entry
attr_reader :search_referrals
#
# initialize
# An LDAP PDU always looks like a BerSequence with
# at least two elements: an integer (message-id number), and
# an application-specific sequence.
# Some LDAPv3 packets also include an optional
# third element, which is a sequence of "controls"
# (See RFC 2251, section 4.1.12).
# The application-specific tag in the sequence tells
# us what kind of packet it is, and each kind has its
# own format, defined in RFC-1777.
# Observe that many clients (such as ldapsearch)
# do not necessarily enforce the expected application
# tags on received protocol packets. This implementation
# does interpret the RFC strictly in this regard, and
# it remains to be seen whether there are servers out
# there that will not work well with our approach.
#
# Added a controls-processor to SearchResult.
# Didn't add it everywhere because it just _feels_
# like it will need to be refactored.
#
def initialize ber_object
begin
@msg_id = ber_object[0].to_i
@app_tag = ber_object[1].ber_identifier - 0x60
rescue
# any error becomes a data-format error
raise LdapPduError.new( "ldap-pdu format error" )
end
case @app_tag
when BindResult
parse_ldap_result ber_object[1]
when SearchReturnedData
parse_search_return ber_object[1]
when SearchResultReferral
parse_search_referral ber_object[1]
when SearchResult
parse_ldap_result ber_object[1]
parse_controls(ber_object[2]) if ber_object[2]
when ModifyResponse
parse_ldap_result ber_object[1]
when AddResponse
parse_ldap_result ber_object[1]
when DeleteResponse
parse_ldap_result ber_object[1]
when ModifyRDNResponse
parse_ldap_result ber_object[1]
else
raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" )
end
end
#
# result_code
# This returns an LDAP result code taken from the PDU,
# but it will be nil if there wasn't a result code.
# That can easily happen depending on the type of packet.
#
def result_code code = :resultCode
@ldap_result and @ldap_result[code]
end
# Return RFC-2251 Controls if any.
# Messy. Does this functionality belong somewhere else?
def result_controls
@ldap_controls || []
end
#
# parse_ldap_result
#
def parse_ldap_result sequence
sequence.length >= 3 or raise LdapPduError
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
end
private :parse_ldap_result
#
# parse_search_return
# Definition from RFC 1777 (we're handling application-4 here)
#
# Search Response ::=
# CHOICE {
# entry [APPLICATION 4] SEQUENCE {
# objectName LDAPDN,
# attributes SEQUENCE OF SEQUENCE {
# AttributeType,
# SET OF AttributeValue
# }
# },
# resultCode [APPLICATION 5] LDAPResult
# }
#
# We concoct a search response that is a hash of the returned attribute values.
# NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
# This is to make them more predictable for user programs, but it
# may not be a good idea. Maybe this should be configurable.
# ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes,
# we also return @search_entry, which is an LDAP::Entry object.
# If that works out well, then we'll remove the first two.
#
# Provisionally removed obsolete search_attributes and search_dn, 04May06.
#
def parse_search_return sequence
sequence.length >= 2 or raise LdapPduError
@search_entry = LDAP::Entry.new( sequence[0] )
#@search_dn = sequence[0]
#@search_attributes = {}
sequence[1].each {|seq|
@search_entry[seq[0]] = seq[1]
#@search_attributes[seq[0].downcase.intern] = seq[1]
}
end
#
# A search referral is a sequence of one or more LDAP URIs.
# Any number of search-referral replies can be returned by the server, interspersed
# with normal replies in any order.
# Until I can think of a better way to do this, we'll return the referrals as an array.
# It'll be up to higher-level handlers to expose something reasonable to the client.
def parse_search_referral uris
@search_referrals = uris
end
# Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
# of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
# Octet String. If only two fields are given, the second one may be
# either criticality or data, since criticality has a default value.
# Someday we may want to come back here and add support for some of
# more-widely used controls. RFC-2696 is a good example.
#
def parse_controls sequence
@ldap_controls = sequence.map do |control|
o = OpenStruct.new
o.oid,o.criticality,o.value = control[0],control[1],control[2]
if o.criticality and o.criticality.is_a?(String)
o.value = o.criticality
o.criticality = false
end
o
end
end
private :parse_controls
end
end # module Net

View File

@ -1,65 +0,0 @@
#-- encoding: UTF-8
# $Id: psw.rb 73 2006-04-24 21:59:35Z blackhedd $
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
class LDAP
class Password
class << self
# Generate a password-hash suitable for inclusion in an LDAP attribute.
# Pass a hash type (currently supported: :md5 and :sha) and a plaintext
# password. This function will return a hashed representation.
# STUB: This is here to fulfill the requirements of an RFC, which one?
# TODO, gotta do salted-sha and (maybe) salted-md5.
# Should we provide sha1 as a synonym for sha1? I vote no because then
# should you also provide ssha1 for symmetry?
def generate( type, str )
case type
when :md5
require 'md5'
"{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }"
when :sha
require 'sha1'
"{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }"
# when ssha
else
raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" )
end
end
end
end
end # class LDAP
end # module Net

View File

@ -1,40 +0,0 @@
#-- encoding: UTF-8
# $Id: ldif.rb 78 2006-04-26 02:57:34Z blackhedd $
#
# Net::LDIF for Ruby
#
#
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#
# THIS FILE IS A STUB.
module Net
class LDIF
end # class LDIF
end # module Net

View File

@ -1,43 +0,0 @@
#-- encoding: UTF-8
# $Id: testber.rb 57 2006-04-18 00:18:48Z blackhedd $
#
#
$:.unshift "lib"
require 'net/ldap'
require 'stringio'
class TestBer < Test::Unit::TestCase
def setup
end
# TODO: Add some much bigger numbers
# 5000000000 is a Bignum, which hits different code.
def test_ber_integers
assert_equal( "\002\001\005", 5.to_ber )
assert_equal( "\002\002\203t", 500.to_ber )
assert_equal( "\002\003\203\206P", 50000.to_ber )
assert_equal( "\002\005\222\320\227\344\000", 5000000000.to_ber )
end
def test_ber_parsing
assert_equal( 6, "\002\001\006".read_ber( Net::LDAP::AsnSyntax ))
assert_equal( "testing", "\004\007testing".read_ber( Net::LDAP::AsnSyntax ))
end
def test_ber_parser_on_ldap_bind_request
s = StringIO.new "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus"
assert_equal( [1, [3, "Administrator", "ad_is_bogus"]], s.read_ber( Net::LDAP::AsnSyntax ))
end
end

View File

@ -1,101 +0,0 @@
# $Id: testdata.ldif 50 2006-04-17 17:57:33Z blackhedd $
#
# This is test-data for an LDAP server in LDIF format.
#
dn: dc=bayshorenetworks,dc=com
objectClass: dcObject
objectClass: organization
o: Bayshore Networks LLC
dc: bayshorenetworks
dn: cn=Manager,dc=bayshorenetworks,dc=com
objectClass: organizationalrole
cn: Manager
dn: ou=people,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: people
dn: ou=privileges,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: privileges
dn: ou=roles,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: roles
dn: ou=office,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: office
dn: mail=nogoodnik@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
cn: Bob Fosse
mail: nogoodnik@steamheat.net
sn: Fosse
ou: people
objectClass: top
objectClass: inetorgperson
objectClass: authorizedperson
hasAccessRole: uniqueIdentifier=engineer,ou=roles
hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles
hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles
hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles
hasAccessRole: uniqueIdentifier=brandplace_logging_user,ou=roles
hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles
hasAccessRole: uniqueIdentifier=workorder_user,ou=roles
hasAccessRole: uniqueIdentifier=bayshore_eagle_user,ou=roles
hasAccessRole: uniqueIdentifier=bayshore_eagle_superuser,ou=roles
hasAccessRole: uniqueIdentifier=kledaras_user,ou=roles
dn: mail=elephant@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
cn: Gwen Verdon
mail: elephant@steamheat.net
sn: Verdon
ou: people
objectClass: top
objectClass: inetorgperson
objectClass: authorizedperson
hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles
hasAccessRole: uniqueIdentifier=engineer,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles
dn: uniqueIdentifier=engineering,ou=privileges,dc=bayshorenetworks,dc=com
uniqueIdentifier: engineering
ou: privileges
objectClass: accessPrivilege
dn: uniqueIdentifier=engineer,ou=roles,dc=bayshorenetworks,dc=com
uniqueIdentifier: engineer
ou: roles
objectClass: accessRole
hasAccessPrivilege: uniqueIdentifier=engineering,ou=privileges
dn: uniqueIdentifier=ldapadmin,ou=roles,dc=bayshorenetworks,dc=com
uniqueIdentifier: ldapadmin
ou: roles
objectClass: accessRole
dn: uniqueIdentifier=ldapsuperadmin,ou=roles,dc=bayshorenetworks,dc=com
uniqueIdentifier: ldapsuperadmin
ou: roles
objectClass: accessRole
dn: mail=catperson@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
cn: Sid Sorokin
mail: catperson@steamheat.net
sn: Sorokin
ou: people
objectClass: top
objectClass: inetorgperson
objectClass: authorizedperson
hasAccessRole: uniqueIdentifier=engineer,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles
hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles
hasAccessRole: uniqueIdentifier=workorder_user,ou=roles

View File

@ -1,13 +0,0 @@
#-- encoding: UTF-8
# $Id: testem.rb 121 2006-05-15 18:36:24Z blackhedd $
#
#
require 'test/unit'
require 'tests/testber'
require 'tests/testldif'
require 'tests/testldap'
require 'tests/testpsw'
require 'tests/testfilter'

View File

@ -1,38 +0,0 @@
#-- encoding: UTF-8
# $Id: testfilter.rb 122 2006-05-15 20:03:56Z blackhedd $
#
#
require 'test/unit'
$:.unshift "lib"
require 'net/ldap'
class TestFilter < Test::Unit::TestCase
def setup
end
def teardown
end
def test_rfc_2254
p Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " )
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
p Net::LDAP::Filter.from_rfc2254( "uid<george*" )
p Net::LDAP::Filter.from_rfc2254( "uid <= george*" )
p Net::LDAP::Filter.from_rfc2254( "uid>george*" )
p Net::LDAP::Filter.from_rfc2254( "uid>=george*" )
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
p Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" )
p Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" )
p Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" )
end
end

View File

@ -1,191 +0,0 @@
#-- encoding: UTF-8
# $Id: testldap.rb 65 2006-04-23 01:17:49Z blackhedd $
#
#
$:.unshift "lib"
require 'test/unit'
require 'net/ldap'
require 'stringio'
class TestLdapClient < Test::Unit::TestCase
# TODO: these tests crash and burn if the associated
# LDAP testserver isn't up and running.
# We rely on being able to read a file with test data
# in LDIF format.
# TODO, WARNING: for the moment, this data is in a file
# whose name and location are HARDCODED into the
# instance method load_test_data.
def setup
@host = "127.0.0.1"
@port = 3890
@auth = {
:method => :simple,
:username => "cn=bigshot,dc=bayshorenetworks,dc=com",
:password => "opensesame"
}
@ldif = load_test_data
end
# Get some test data which will be used to validate
# the responses from the test LDAP server we will
# connect to.
# TODO, Bogus: we are HARDCODING the location of the file for now.
#
def load_test_data
ary = File.readlines( "tests/testdata.ldif" )
hash = {}
while line = ary.shift and line.chomp!
if line =~ /^dn:[\s]*/i
dn = $'
hash[dn] = {}
while attr = ary.shift and attr.chomp! and attr =~ /^([\w]+)[\s]*:[\s]*/
hash[dn][$1.downcase.intern] ||= []
hash[dn][$1.downcase.intern] << $'
end
end
end
hash
end
# Binding tests.
# Need tests for all kinds of network failures and incorrect auth.
# TODO: Implement a class-level timeout for operations like bind.
# Search has a timeout defined at the protocol level, other ops do not.
# TODO, use constants for the LDAP result codes, rather than hardcoding them.
def test_bind
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
assert_equal( true, ldap.bind )
assert_equal( 0, ldap.get_operation_result.code )
assert_equal( "Success", ldap.get_operation_result.message )
bad_username = @auth.merge( {:username => "cn=badguy,dc=imposters,dc=com"} )
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_username
assert_equal( false, ldap.bind )
assert_equal( 48, ldap.get_operation_result.code )
assert_equal( "Inappropriate Authentication", ldap.get_operation_result.message )
bad_password = @auth.merge( {:password => "cornhusk"} )
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_password
assert_equal( false, ldap.bind )
assert_equal( 49, ldap.get_operation_result.code )
assert_equal( "Invalid Credentials", ldap.get_operation_result.message )
end
def test_search
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
search = {:base => "dc=smalldomain,dc=com"}
assert_equal( false, ldap.search( search ))
assert_equal( 32, ldap.get_operation_result.code )
search = {:base => "dc=bayshorenetworks,dc=com"}
assert_equal( true, ldap.search( search ))
assert_equal( 0, ldap.get_operation_result.code )
ldap.search( search ) {|res|
assert_equal( res, @ldif )
}
end
# This is a helper routine for test_search_attributes.
def internal_test_search_attributes attrs_to_search
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
assert( ldap.bind )
search = {
:base => "dc=bayshorenetworks,dc=com",
:attributes => attrs_to_search
}
ldif = @ldif
ldif.each {|dn,entry|
entry.delete_if {|attr,value|
! attrs_to_search.include?(attr)
}
}
assert_equal( true, ldap.search( search ))
ldap.search( search ) {|res|
res_keys = res.keys.sort
ldif_keys = ldif.keys.sort
assert( res_keys, ldif_keys )
res.keys.each {|rk|
assert( res[rk], ldif[rk] )
}
}
end
def test_search_attributes
internal_test_search_attributes [:mail]
internal_test_search_attributes [:cn]
internal_test_search_attributes [:ou]
internal_test_search_attributes [:hasaccessprivilege]
internal_test_search_attributes ["mail"]
internal_test_search_attributes ["cn"]
internal_test_search_attributes ["ou"]
internal_test_search_attributes ["hasaccessrole"]
internal_test_search_attributes [:mail, :cn, :ou, :hasaccessrole]
internal_test_search_attributes [:mail, "cn", :ou, "hasaccessrole"]
end
def test_search_filters
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
search = {
:base => "dc=bayshorenetworks,dc=com",
:filter => Net::LDAP::Filter.eq( "sn", "Fosse" )
}
ldap.search( search ) {|res|
p res
}
end
def test_open
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
ldap.open {|ldap|
10.times {
rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
assert_equal( true, rc )
}
}
end
def test_ldap_open
Net::LDAP.open( :host => @host, :port => @port, :auth => @auth ) {|ldap|
10.times {
rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
assert_equal( true, rc )
}
}
end
end

View File

@ -1,70 +0,0 @@
#-- encoding: UTF-8
# $Id: testldif.rb 61 2006-04-18 20:55:55Z blackhedd $
#
#
$:.unshift "lib"
require 'test/unit'
require 'net/ldap'
require 'net/ldif'
require 'sha1'
require 'base64'
class TestLdif < Test::Unit::TestCase
TestLdifFilename = "tests/testdata.ldif"
def test_empty_ldif
ds = Net::LDAP::Dataset::read_ldif( StringIO.new )
assert_equal( true, ds.empty? )
end
def test_ldif_with_comments
str = ["# Hello from LDIF-land", "# This is an unterminated comment"]
io = StringIO.new( str[0] + "\r\n" + str[1] )
ds = Net::LDAP::Dataset::read_ldif( io )
assert_equal( str, ds.comments )
end
def test_ldif_with_password
psw = "goldbricks"
hashed_psw = "{SHA}" + Base64::encode64( SHA1.new(psw).digest ).chomp
ldif_encoded = Base64::encode64( hashed_psw ).chomp
ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: Goldbrick\r\nuserPassword:: #{ldif_encoded}\r\n\r\n" ))
recovered_psw = ds["Goldbrick"][:userpassword].shift
assert_equal( hashed_psw, recovered_psw )
end
def test_ldif_with_continuation_lines
ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: abcdefg\r\n hijklmn\r\n\r\n" ))
assert_equal( true, ds.has_key?( "abcdefg hijklmn" ))
end
# TODO, INADEQUATE. We need some more tests
# to verify the content.
def test_ldif
File.open( TestLdifFilename, "r" ) {|f|
ds = Net::LDAP::Dataset::read_ldif( f )
assert_equal( 13, ds.length )
}
end
# TODO, need some tests.
# Must test folded lines and base64-encoded lines as well as normal ones.
def test_to_ldif
File.open( TestLdifFilename, "r" ) {|f|
ds = Net::LDAP::Dataset::read_ldif( f )
ds.to_ldif
assert_equal( true, false ) # REMOVE WHEN WE HAVE SOME TESTS HERE.
}
end
end

View File

@ -1,29 +0,0 @@
#-- encoding: UTF-8
# $Id: testpsw.rb 72 2006-04-24 21:58:14Z blackhedd $
#
#
$:.unshift "lib"
require 'net/ldap'
require 'stringio'
class TestPassword < Test::Unit::TestCase
def setup
end
def test_psw
assert_equal( "{MD5}xq8jwrcfibi0sZdZYNkSng==", Net::LDAP::Password.generate( :md5, "cashflow" ))
assert_equal( "{SHA}YE4eGkN4BvwNN1f5R7CZz0kFn14=", Net::LDAP::Password.generate( :sha, "cashflow" ))
end
end