Merge branch 'master' into unstable
Conflicts: app/views/projects/index.rhtml lib/chili_project/compatibility.rb
This commit is contained in:
commit
1d21cab2e4
4
Gemfile
4
Gemfile
|
@ -19,6 +19,10 @@ group :test do
|
||||||
platforms :mri_19, :mingw_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
|
platforms :mri_19, :mingw_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
group :ldap do
|
||||||
|
gem "net-ldap", '~> 0.2.2'
|
||||||
|
end
|
||||||
|
|
||||||
group :openid do
|
group :openid do
|
||||||
gem "ruby-openid", '~> 2.1.4', :require => 'openid'
|
gem "ruby-openid", '~> 2.1.4', :require => 'openid'
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,8 +16,8 @@ class UsersController < ApplicationController
|
||||||
layout 'admin'
|
layout 'admin'
|
||||||
|
|
||||||
before_filter :require_admin, :except => :show
|
before_filter :require_admin, :except => :show
|
||||||
before_filter :find_user, :only => [:show, :edit, :update, :edit_membership, :destroy_membership]
|
before_filter :find_user, :only => [:show, :edit, :update, :destroy, :edit_membership, :destroy_membership]
|
||||||
accept_key_auth :index, :show, :create, :update
|
accept_key_auth :index, :show, :create, :update, :destroy
|
||||||
|
|
||||||
include SortHelper
|
include SortHelper
|
||||||
include CustomFieldsHelper
|
include CustomFieldsHelper
|
||||||
|
@ -178,6 +178,24 @@ class UsersController < ApplicationController
|
||||||
redirect_to :controller => 'users', :action => 'edit', :id => @user
|
redirect_to :controller => 'users', :action => 'edit', :id => @user
|
||||||
end
|
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
|
def edit_membership
|
||||||
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
|
@membership = Member.edit_membership(params[:membership_id], params[:membership], @user)
|
||||||
@membership.save if request.post?
|
@membership.save if request.post?
|
||||||
|
|
|
@ -414,7 +414,7 @@ module ApplicationHelper
|
||||||
title = []
|
title = []
|
||||||
title << h(@project.name) if @project
|
title << h(@project.name) if @project
|
||||||
title += @html_title if @html_title
|
title += @html_title if @html_title
|
||||||
title << Setting.app_title
|
title << h(Setting.app_title)
|
||||||
title.select {|t| !t.blank? }.join(' - ')
|
title.select {|t| !t.blank? }.join(' - ')
|
||||||
else
|
else
|
||||||
@html_title ||= []
|
@html_title ||= []
|
||||||
|
|
|
@ -37,13 +37,26 @@ module UsersHelper
|
||||||
def change_status_link(user)
|
def change_status_link(user)
|
||||||
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
|
url = {:controller => 'users', :action => 'update', :id => user, :page => params[:page], :status => params[:status], :tab => nil}
|
||||||
|
|
||||||
|
links = []
|
||||||
if user.locked?
|
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?
|
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
|
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
|
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
|
end
|
||||||
|
|
||||||
def user_settings_tabs
|
def user_settings_tabs
|
||||||
|
|
|
@ -108,7 +108,7 @@ class Journal < ActiveRecord::Base
|
||||||
## => Try the journaled object with the same method and arguments
|
## => Try the journaled object with the same method and arguments
|
||||||
## => On error, call super
|
## => On error, call super
|
||||||
def method_missing(method, *args, &block)
|
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)
|
journaled.send(method, *args, &block)
|
||||||
rescue NoMethodError => e
|
rescue NoMethodError => e
|
||||||
e.name == method ? super : raise(e)
|
e.name == method ? super : raise(e)
|
||||||
|
|
|
@ -396,8 +396,8 @@ class Mailer < ActionMailer::Base
|
||||||
# if he doesn't want to receive notifications about what he does
|
# if he doesn't want to receive notifications about what he does
|
||||||
@author ||= User.current
|
@author ||= User.current
|
||||||
if @author.pref[:no_self_notified]
|
if @author.pref[:no_self_notified]
|
||||||
recipients.delete(@author.mail) if recipients
|
recipients((recipients.is_a?(Array) ? recipients : [recipients]) - [@author.mail]) if recipients.present?
|
||||||
cc.delete(@author.mail) if cc
|
cc((cc.is_a?(Array) ? cc : [cc]) - [@author.mail]) if cc.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
notified_users = [recipients, cc].flatten.compact.uniq
|
notified_users = [recipients, cc].flatten.compact.uniq
|
||||||
|
|
|
@ -70,6 +70,7 @@ class User < Principal
|
||||||
validates_length_of :mail, :maximum => 60, :allow_nil => true
|
validates_length_of :mail, :maximum => 60, :allow_nil => true
|
||||||
validates_confirmation_of :password, :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 :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|
|
named_scope :in_group, lambda {|group|
|
||||||
group_id = group.is_a?(Group) ? group.id : group.to_i
|
group_id = group.is_a?(Group) ? group.id : group.to_i
|
||||||
|
@ -207,6 +208,11 @@ class User < Principal
|
||||||
update_attribute(:status, STATUS_LOCKED)
|
update_attribute(:status, STATUS_LOCKED)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deletable?
|
||||||
|
registered? && last_login_on.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# Returns true if +clear_password+ is the correct user's password, otherwise false
|
# Returns true if +clear_password+ is the correct user's password, otherwise false
|
||||||
def check_password?(clear_password)
|
def check_password?(clear_password)
|
||||||
if auth_source_id.present?
|
if auth_source_id.present?
|
||||||
|
@ -526,6 +532,24 @@ class User < Principal
|
||||||
if !password.nil? && password.size < Setting.password_min_length.to_i
|
if !password.nil? && password.size < Setting.password_min_length.to_i
|
||||||
errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
|
errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -104,7 +104,12 @@ class WikiContent < ActiveRecord::Base
|
||||||
def text
|
def text
|
||||||
@text ||= case changes["compression"]
|
@text ||= case changes["compression"]
|
||||||
when "gzip"
|
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
|
else
|
||||||
# uncompressed data
|
# uncompressed data
|
||||||
changes["data"]
|
changes["data"]
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
|
<%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if authorize_for('timelog', 'edit') %>
|
<% if User.current.allowed_to?(:log_time, @project) %>
|
||||||
<fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
|
<fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
|
||||||
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
|
<% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
|
||||||
<div class="splitcontentleft">
|
<div class="splitcontentleft">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
<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="description" content="<%= Redmine::Info.app_name %>" />
|
||||||
<meta name="keywords" content="issue,bug,tracker" />
|
<meta name="keywords" content="issue,bug,tracker" />
|
||||||
<%= csrf_meta_tag %>
|
<%= csrf_meta_tag %>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<%= 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_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_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' }%>
|
<%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%>
|
||||||
|
<%= call_hook(:view_projects_show_contextual) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2><%=l(:label_project_plural)%></h2>
|
<h2><%=l(:label_project_plural)%></h2>
|
||||||
|
@ -16,6 +17,8 @@
|
||||||
<%= textilizable Setting.welcome_text %>
|
<%= textilizable Setting.welcome_text %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%= call_hook(:view_projects_show_top) %>
|
||||||
|
|
||||||
<%= render_project_hierarchy(@projects)%>
|
<%= render_project_hierarchy(@projects)%>
|
||||||
|
|
||||||
<% if User.current.logged? %>
|
<% if User.current.logged? %>
|
||||||
|
|
|
@ -97,11 +97,11 @@ Event.observe(document,"dom:loaded", apply_filters_observer);
|
||||||
</select>
|
</select>
|
||||||
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
|
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select('#{field}');", :style => "vertical-align: bottom;" %>
|
||||||
<% when :date, :date_past %>
|
<% 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 %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">toggle_filter('<%= field %>');</script>
|
<script type="text/javascript">toggle_filter('<%= field %>');</script>
|
||||||
|
|
|
@ -960,26 +960,26 @@ bg:
|
||||||
field_effective_date: Дата
|
field_effective_date: Дата
|
||||||
text_default_encoding: "По подразбиране: UTF-8"
|
text_default_encoding: "По подразбиране: UTF-8"
|
||||||
text_git_repo_example: a bare and local repository (e.g. /gitrepo, c:\gitrepo)
|
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: Кодиране на пътищата
|
label_path_encoding: Кодиране на пътищата
|
||||||
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
|
text_mercurial_repo_example: локално хранилище (например /hgrepo, c:\hgrepo)
|
||||||
label_diff: diff
|
label_diff: diff
|
||||||
setting_issue_startdate_is_adddate: Use current date as start date for new issues
|
setting_issue_startdate_is_adddate: Използване на текущата дата като начална дата за нови задачи
|
||||||
description_search: Searchfield
|
description_search: Търсене
|
||||||
description_user_mail_notification: Mail notification settings
|
description_user_mail_notification: Конфигурация известията по пощата
|
||||||
description_date_range_list: Choose range from list
|
description_date_range_list: Изберете диапазон от списъка
|
||||||
description_date_to: Enter end date
|
description_date_to: Въведете крайна дата
|
||||||
description_query_sort_criteria_attribute: Sort attribute
|
description_query_sort_criteria_attribute: Атрибут на сортиране
|
||||||
description_message_content: Message content
|
description_message_content: Съдържание на съобщението
|
||||||
description_wiki_subpages_reassign: Choose new parent page
|
description_wiki_subpages_reassign: Изберете нова родителска страница
|
||||||
description_available_columns: Available Columns
|
description_available_columns: Налични колони
|
||||||
description_selected_columns: Selected Columns
|
description_selected_columns: Избрани колони
|
||||||
description_date_range_interval: Choose range by selecting start and end date
|
description_date_range_interval: Изберете диапазон чрез задаване на начална и крайна дати
|
||||||
description_project_scope: Search scope
|
description_project_scope: Обхват на търсенето
|
||||||
description_issue_category_reassign: Choose issue category
|
description_issue_category_reassign: Изберете категория
|
||||||
description_query_sort_criteria_direction: Sort direction
|
description_query_sort_criteria_direction: Посока на сортиране
|
||||||
description_notes: Notes
|
description_notes: Бележки
|
||||||
description_filter: Filter
|
description_filter: Филтър
|
||||||
description_choose_project: Projects
|
description_choose_project: Проекти
|
||||||
description_date_from: Enter start date
|
description_date_from: Въведете начална дата
|
||||||
label_deleted_custom_field: (deleted custom field)
|
label_deleted_custom_field: (изтрито потребителско поле)
|
||||||
|
|
|
@ -19,9 +19,9 @@ rescue LoadError
|
||||||
raise "Could not load the bundler gem. Install it with `gem install bundler`."
|
raise "Could not load the bundler gem. Install it with `gem install bundler`."
|
||||||
end
|
end
|
||||||
|
|
||||||
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
|
if Gem::Version.new(Bundler::VERSION) < Gem::Version.new("1.0.6")
|
||||||
raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
|
raise RuntimeError, "Your bundler version is too old. We require " +
|
||||||
"Run `gem install bundler` to upgrade."
|
"at least version 1.0.6. Run `gem install bundler` to upgrade."
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|
|
@ -137,8 +137,7 @@ ActionController::Routing::Routes.draw do |map|
|
||||||
map.resources :users, :member => {
|
map.resources :users, :member => {
|
||||||
:edit_membership => :post,
|
:edit_membership => :post,
|
||||||
:destroy_membership => :post
|
:destroy_membership => :post
|
||||||
},
|
}
|
||||||
:except => [:destroy]
|
|
||||||
|
|
||||||
# For nice "roadmap" in the url for the index action
|
# For nice "roadmap" in the url for the index action
|
||||||
map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
|
map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
|
||||||
|
|
211213
doc/CHANGELOG.rdoc
211213
doc/CHANGELOG.rdoc
File diff suppressed because it is too large
Load Diff
|
@ -438,10 +438,12 @@ sub is_member {
|
||||||
|
|
||||||
my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
|
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 $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
|
||||||
my $usrprojpass;
|
my $usrprojpass;
|
||||||
if ($cfg->{RedmineCacheCredsMax}) {
|
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));
|
return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
|
||||||
}
|
}
|
||||||
my $query = $cfg->{RedmineQuery};
|
my $query = $cfg->{RedmineQuery};
|
||||||
|
@ -485,10 +487,10 @@ sub is_member {
|
||||||
|
|
||||||
if ($cfg->{RedmineCacheCredsMax} and $ret) {
|
if ($cfg->{RedmineCacheCredsMax} and $ret) {
|
||||||
if (defined $usrprojpass) {
|
if (defined $usrprojpass) {
|
||||||
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
|
$cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id.":".$access_mode, $pass_digest);
|
||||||
} else {
|
} else {
|
||||||
if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
|
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}++;
|
$cfg->{RedmineCacheCredsCount}++;
|
||||||
} else {
|
} else {
|
||||||
$cfg->{RedmineCacheCreds}->clear();
|
$cfg->{RedmineCacheCreds}->clear();
|
||||||
|
|
|
@ -37,5 +37,10 @@ module ChiliProject
|
||||||
def self.using_liquid?
|
def self.using_liquid?
|
||||||
true
|
true
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ module ChiliProject
|
||||||
module VERSION #:nodoc:
|
module VERSION #:nodoc:
|
||||||
|
|
||||||
MAJOR = 2
|
MAJOR = 2
|
||||||
MINOR = 4
|
MINOR = 5
|
||||||
PATCH = 0
|
PATCH = 0
|
||||||
TINY = PATCH # Redmine compat
|
TINY = PATCH # Redmine compat
|
||||||
|
|
||||||
|
|
|
@ -100,10 +100,10 @@ Redmine::AccessControl.map do |map|
|
||||||
end
|
end
|
||||||
|
|
||||||
map.project_module :time_tracking do |map|
|
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 :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_time_entries, {:timelog => [:edit, :update, :destroy]}, :require => :member
|
||||||
map.permission :edit_own_time_entries, {:timelog => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin
|
map.permission :edit_own_time_entries, {:timelog => [:edit, :update, :destroy]}, :require => :loggedin
|
||||||
map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
|
map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
|
||||||
super
|
super
|
||||||
end
|
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
|
src = <<-END_SRC
|
||||||
def #{selector}(field, options = {})
|
def #{selector}(field, options = {})
|
||||||
label_for_field(field, options) + super
|
label_for_field(field, options) + super
|
||||||
|
|
|
@ -781,6 +781,22 @@ class IssuesControllerTest < ActionController::TestCase
|
||||||
assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
|
assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
|
||||||
end
|
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
|
def test_update_edit_form
|
||||||
@request.session[:user_id] = 2
|
@request.session[:user_id] = 2
|
||||||
xhr :post, :new, :project_id => 1,
|
xhr :post, :new, :project_id => 1,
|
||||||
|
|
|
@ -111,6 +111,18 @@ class TimelogControllerTest < ActionController::TestCase
|
||||||
assert_equal 3, t.user_id
|
assert_equal 3, t.user_id
|
||||||
end
|
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
|
def test_update
|
||||||
entry = TimeEntry.find(1)
|
entry = TimeEntry.find(1)
|
||||||
assert_equal 1, entry.issue_id
|
assert_equal 1, entry.issue_id
|
||||||
|
|
|
@ -270,6 +270,31 @@ class UsersControllerTest < ActionController::TestCase
|
||||||
assert u.check_password?('newpass')
|
assert u.check_password?('newpass')
|
||||||
end
|
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
|
def test_edit_membership
|
||||||
post :edit_membership, :id => 2, :membership_id => 1,
|
post :edit_membership, :id => 2, :membership_id => 1,
|
||||||
:membership => { :role_ids => [2]}
|
:membership => { :role_ids => [2]}
|
||||||
|
|
|
@ -241,26 +241,52 @@ class ApiTest::UsersTest < ActionController::IntegrationTest
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
assert_response :method_not_allowed
|
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_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
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context ".json" do
|
context ".json" do
|
||||||
should "not be allowed" do
|
should "delete the user" do
|
||||||
assert_no_difference('User.count') do
|
u = User.new(:firstname => 'Death', :lastname => 'Row', :mail => 'death.row@example.com', :language => 'en')
|
||||||
delete '/users/2.json'
|
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
|
end
|
||||||
|
|
||||||
assert_response :method_not_allowed
|
assert_response :success
|
||||||
|
assert_nil User.find_by_id(u.id)
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,4 +58,15 @@ class LayoutTest < ActionController::IntegrationTest
|
||||||
:attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}},
|
:attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}},
|
||||||
:parent => {:tag => 'head'}
|
:parent => {:tag => 'head'}
|
||||||
end
|
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&A/
|
||||||
|
assert_select "title", /<3/
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -174,6 +174,14 @@ class UserTest < ActiveSupport::TestCase
|
||||||
assert_equal nil, user
|
assert_equal nil, user
|
||||||
end
|
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 ".try_to_login" do
|
||||||
context "with good credentials" do
|
context "with good credentials" do
|
||||||
should "return the user" do
|
should "return the user" do
|
||||||
|
|
|
@ -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.
|
|
|
@ -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
|
|
|
@ -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.
|
|
|
@ -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
|
|
|
@ -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
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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'
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue