Index: app/helpers/auth_sources_ids_helper.rb =================================================================== --- app/helpers/auth_sources_ids_helper.rb (revision 0) +++ app/helpers/auth_sources_ids_helper.rb (revision 0) @@ -0,0 +1,19 @@ + +require 'osx/cocoa' + +OSX.require_framework 'Collaboration' + +module AuthSourcesIdsHelper + + # localized to the server, not the web app user + def authorities + @@authorities ||= [ OSX::CBIdentityAuthority.defaultIdentityAuthority.localizedName, + OSX::CBIdentityAuthority.managedIdentityAuthority.localizedName, + OSX::CBIdentityAuthority.localIdentityAuthority.localizedName ] + end + + def ids_authority_select(auth_source) + select("auth_source","host",authorities.collect { |x| [ x, x ] }) + end + +end Index: app/models/auth_source_ids.rb =================================================================== --- app/models/auth_source_ids.rb (revision 0) +++ app/models/auth_source_ids.rb (revision 0) @@ -0,0 +1,158 @@ +# Support for Mac OS X v10.5 Identity Services + +require 'osx/cocoa' +OSX.require_framework 'Collaboration' + +class AuthSourceIds < AuthSource + validates_presence_of :host + validates_length_of :name, :attr_login, :maximum => 60, :allow_nil => true + + def authenticate(login, password) + return nil if login.blank? || password.blank? + attrs = [] + # get authority + authority = authority_for_name!(self.host) + # search for user + identity = OSX::CBIdentity.identityWithName_authority(login, authority) + return nil if identity.nil? || !identity.is_a?(OSX::CBUserIdentity) + # get attributes if creating new user + if onthefly_register? + first, last = fullname_to_first_last(fullname_for_identity(identity)) + attrs = [:firstname => first, + :lastname => last, + :mail => mail_for_identity(identity), + :auth_source_id => self.id] + end + # authenticate user + return nil unless identity.authenticateWithPassword(password) + # filter users + return nil unless self.attr_login.empty? || identity.isMemberOfGroup(group_for_name!(self.attr_login, authority)) + # return attributes + attrs + end + + def auth_method_name + "Identity Services" + end + + def test_connection + authority = authority_for_name!(self.host) + # also check filtered group + group_for_name!(self.attr_login, authority) unless self.attr_login.empty? + end + + private + + def group_for_name!(group_name, authority) + group = group_for_name(group_name, authority) + raise "Identity Services Group \"#{group_name}\" not found" if group.nil? + group + end + + def group_for_name(group_name, authority) + group = OSX::CBIdentity.identityWithName_authority(group_name, authority) + group.is_a?(OSX::CBGroupIdentity) ? group : nil + end + + def authority_for_name!(auth_name) + authority = authority_for_name(auth_name) + raise "Identity Services Authority \"#{auth_name}\" not found" if authority.nil? + authority + end + + def authority_for_name(auth_name) + case auth_name + when OSX::CBIdentityAuthority.defaultIdentityAuthority.localizedName: OSX::CBIdentityAuthority.defaultIdentityAuthority + when OSX::CBIdentityAuthority.managedIdentityAuthority.localizedName: OSX::CBIdentityAuthority.managedIdentityAuthority + when OSX::CBIdentityAuthority.localIdentityAuthority.localizedName: OSX::CBIdentityAuthority.localIdentityAuthority + else nil + end + end + + def fullname_for_identity(identity) + fullname = identity.fullName + fullname && fullname.to_ruby + end + + def mail_for_identity(identity) + email = identity.emailAddress + return nil if email.nil? + preferred_email = email.to_ruby + if self.tls # try for local address if possible + user_aliases = identity.aliases.to_ruby + host_parts = Socket.gethostname.split(".") + while host_parts.size > 1 + suffix = "@" + host_parts.join(".") + user_aliases.each do |a| + if a[-suffix.length, suffix.length] == suffix + preferred_email = a + break + end + end + host_parts.delete_at(0) + end + end + preferred_email + end + + def part_is_suffix(part) + @@suffixes ||= [ "JR.", "SR.", "JR", "SR", "II", "III", "3RD","IV", "V", "VI", "VII" ] + @@suffixes.include?(part.upcase) + end + + def part_is_lastname(part) + @@lastnames ||= [ "DE", "DER", "DI", "LA", "LE", "MAC", "MC", "VAN", "VON", "PONCE" ] + @@lastnames.include?(part.upcase) + end + + def fullname_to_first_last(fullname) + return [nil, nil] if fullname.nil? + + suffix = nil + firstname = "" + lastname = "" + + # split on comma + parts = fullname.split(",").map { |i| i.strip } + + # got at least one comma - check for suffix + if parts.size > 1 && part_is_suffix(parts[-1]) + suffix = ", " + parts.slice!(-1) + end + + if parts.size == 1 + firstparts = [] + lastparts = [] + # need to determine where first and last name splits + parts = parts.first.split(" ") + # take care of suffix (if any) + if parts.size > 1 && part_is_suffix(parts[-1]) + suffix = " " + parts.slice!(-1) + end + # last name + if parts.size > 0 + lastparts.unshift(parts.slice!(-1)) + end + # first name + if parts.size > 0 + firstparts.push(parts.slice!(0)) + end + # all the rest + while parts.size > 0 && part_is_lastname(parts.slice(-1)) + lastparts.unshift(parts.slice!(-1)) + end + # make strings + firstparts.push(parts) if parts.size > 0 + firstname = firstparts.join(" ") + firstname += suffix unless suffix.nil? + lastname = lastparts.join(" ") + elsif parts.size > 1 + # now have last, first + lastname = parts.slice!(0) + firstname = parts.join(", ") + firstname += suffix unless suffix.nil? + end + [firstname, lastname] + end + +end Index: app/controllers/auth_sources_controller.rb =================================================================== --- app/controllers/auth_sources_controller.rb (revision 1907) +++ app/controllers/auth_sources_controller.rb (working copy) @@ -17,6 +17,7 @@ class AuthSourcesController < ApplicationController before_filter :require_admin + helper :auth_sources_ids def index list @@ -33,11 +34,17 @@ end def new - @auth_source = AuthSourceLdap.new + @auth_source = case params[:type] # parameter passed in url + when "AuthSourceIds": AuthSourceIds.new + else AuthSourceLdap.new + end end def create - @auth_source = AuthSourceLdap.new(params[:auth_source]) + @auth_source = case params[:auth_source][:type] + when "AuthSourceIds": AuthSourceIds.new(params[:auth_source]) + else AuthSourceLdap.new(params[:auth_source]) + end if @auth_source.save flash[:notice] = l(:notice_successful_create) redirect_to :action => 'list' Index: app/views/auth_sources/list.rhtml =================================================================== --- app/views/auth_sources/list.rhtml (revision 1907) +++ app/views/auth_sources/list.rhtml (working copy) @@ -1,5 +1,6 @@
-<%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %> +<%= link_to l(:label_auth_source_new_ldap), {:action => 'new'}, :class => 'icon icon-add' %> +<%= link_to l(:label_auth_source_new_ids), {:action => 'new', :type => 'AuthSourceIds'}, :class => 'icon icon-add' %>

<%=l(:label_auth_source_plural)%>

@@ -8,7 +9,7 @@ <%=l(:field_name)%> <%=l(:field_type)%> - <%=l(:field_host)%> + <%=l(:field_host_authority)%> Index: app/views/auth_sources/_form.rhtml =================================================================== --- app/views/auth_sources/_form.rhtml (revision 1907) +++ app/views/auth_sources/_form.rhtml (working copy) @@ -1,44 +0,0 @@ -<%= error_messages_for 'auth_source' %> - -
- -

-<%= text_field 'auth_source', 'name' %>

- -

-<%= text_field 'auth_source', 'host' %>

- -

-<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

- -

-<%= text_field 'auth_source', 'account' %>

- -

-<%= password_field 'auth_source', 'account_password', :name => 'ignore', - :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), - :onfocus => "this.value=''; this.name='auth_source[account_password]';", - :onchange => "this.name='auth_source[account_password]';" %>

- -

-<%= text_field 'auth_source', 'base_dn', :size => 60 %>

- -

-<%= check_box 'auth_source', 'onthefly_register' %>

-
- -
<%=l(:label_attribute_plural)%> -

-<%= text_field 'auth_source', 'attr_login', :size => 20 %>

- -

-<%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

- -

-<%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

- -

-<%= text_field 'auth_source', 'attr_mail', :size => 20 %>

-
- - Index: app/views/auth_sources/_form_AuthSourceIds.rhtml =================================================================== --- app/views/auth_sources/_form_AuthSourceIds.rhtml (revision 0) +++ app/views/auth_sources/_form_AuthSourceIds.rhtml (revision 0) @@ -0,0 +1,26 @@ +<%= error_messages_for 'auth_source' %> + +
+ +<%= hidden_field 'auth_source', 'type' %> +

+<%= text_field 'auth_source', 'name' %>

+ +

+<%= ids_authority_select(@auth_source.host) %>

+ +

+<%= check_box 'auth_source', 'onthefly_register' %>

+
+ +
<%=l(:label_options)%> +

+<%= check_box 'auth_source', 'tls' %>

+ +

+<%= text_field 'auth_source', 'attr_login', :size => 30 %>

+ +
+ + + Index: app/views/auth_sources/edit.rhtml =================================================================== --- app/views/auth_sources/edit.rhtml (revision 1907) +++ app/views/auth_sources/edit.rhtml (working copy) @@ -1,7 +1,7 @@

<%=l(:label_auth_source)%> (<%= @auth_source.auth_method_name %>)

<% form_tag({:action => 'update', :id => @auth_source}, :class => "tabular") do %> - <%= render :partial => 'form' %> + <%= render :partial => 'form_' + @auth_source.class.to_s %> <%= submit_tag l(:button_save) %> <% end %> Index: app/views/auth_sources/_form_AuthSourceLdap.rhtml =================================================================== --- app/views/auth_sources/_form_AuthSourceLdap.rhtml (revision 1907) +++ app/views/auth_sources/_form_AuthSourceLdap.rhtml (working copy) @@ -2,6 +2,7 @@
+<%= hidden_field 'auth_source', 'type' %>

<%= text_field 'auth_source', 'name' %>

Index: app/views/auth_sources/new.rhtml =================================================================== --- app/views/auth_sources/new.rhtml (revision 1907) +++ app/views/auth_sources/new.rhtml (working copy) @@ -1,6 +1,6 @@

<%=l(:label_auth_source_new)%> (<%= @auth_source.auth_method_name %>)

<% form_tag({:action => 'create'}, :class => "tabular") do %> - <%= render :partial => 'form' %> + <%= render :partial => 'form_' + @auth_source.class.to_s %> <%= submit_tag l(:button_create) %> <% end %> Index: lang/en.yml =================================================================== --- lang/en.yml (revision 1907) +++ lang/en.yml (working copy) @@ -152,6 +152,8 @@ field_version: Version field_type: Type field_host: Host +field_host_authority: Host/Authority +field_authority: Authority field_port: Port field_account: Account field_base_dn: Base DN @@ -160,9 +162,11 @@ field_attr_lastname: Lastname attribute field_attr_mail: Email attribute field_onthefly: On-the-fly user creation +field_prefer_local_email: Prefer local email +field_restrict_user_group: Restrict Users to Group field_start_date: Start field_done_ratio: %% Done -field_auth_source: Authentication mode +field_auth_source: Authentication source field_hide_mail: Hide my email address field_comments: Comment field_url: URL @@ -297,9 +301,11 @@ label_logged_as: Logged in as label_environment: Environment label_authentication: Authentication -label_auth_source: Authentication mode -label_auth_source_new: New authentication mode -label_auth_source_plural: Authentication modes +label_auth_source: Authentication source +label_auth_source_new: New authentication source +label_auth_source_new_ldap: New LDAP authentication source +label_auth_source_new_ids: New Identity Services authentication source +label_auth_source_plural: Authentication sources label_subproject_plural: Subprojects label_and_its_subprojects: %s and its subprojects label_min_max_length: Min - Max length @@ -516,7 +522,7 @@ label_more: More label_scm: SCM label_plugins: Plugins -label_ldap_authentication: LDAP authentication +label_ldap_authentication: External authentication label_downloads_abbr: D/L label_optional_description: Optional description label_add_another_file: Add another file