Mac OS X Identity Services Authentication Hack ยป redmine_identity_services_hack.diff
| app/helpers/auth_sources_ids_helper.rb (revision 0) | ||
|---|---|---|
| 1 | ||
| 2 |
require 'osx/cocoa' |
|
| 3 | ||
| 4 |
OSX.require_framework 'Collaboration' |
|
| 5 | ||
| 6 |
module AuthSourcesIdsHelper |
|
| 7 | ||
| 8 |
# localized to the server, not the web app user |
|
| 9 |
def authorities |
|
| 10 |
@@authorities ||= [ OSX::CBIdentityAuthority.defaultIdentityAuthority.localizedName, |
|
| 11 |
OSX::CBIdentityAuthority.managedIdentityAuthority.localizedName, |
|
| 12 |
OSX::CBIdentityAuthority.localIdentityAuthority.localizedName ] |
|
| 13 |
end |
|
| 14 | ||
| 15 |
def ids_authority_select(auth_source) |
|
| 16 |
select("auth_source","host",authorities.collect { |x| [ x, x ] })
|
|
| 17 |
end |
|
| 18 | ||
| 19 |
end |
|
| app/models/auth_source_ids.rb (revision 0) | ||
|---|---|---|
| 1 |
# Support for Mac OS X v10.5 Identity Services |
|
| 2 | ||
| 3 |
require 'osx/cocoa' |
|
| 4 |
OSX.require_framework 'Collaboration' |
|
| 5 | ||
| 6 |
class AuthSourceIds < AuthSource |
|
| 7 |
validates_presence_of :host |
|
| 8 |
validates_length_of :name, :attr_login, :maximum => 60, :allow_nil => true |
|
| 9 | ||
| 10 |
def authenticate(login, password) |
|
| 11 |
return nil if login.blank? || password.blank? |
|
| 12 |
attrs = [] |
|
| 13 |
# get authority |
|
| 14 |
authority = authority_for_name!(self.host) |
|
| 15 |
# search for user |
|
| 16 |
identity = OSX::CBIdentity.identityWithName_authority(login, authority) |
|
| 17 |
return nil if identity.nil? || !identity.is_a?(OSX::CBUserIdentity) |
|
| 18 |
# get attributes if creating new user |
|
| 19 |
if onthefly_register? |
|
| 20 |
first, last = fullname_to_first_last(fullname_for_identity(identity)) |
|
| 21 |
attrs = [:firstname => first, |
|
| 22 |
:lastname => last, |
|
| 23 |
:mail => mail_for_identity(identity), |
|
| 24 |
:auth_source_id => self.id] |
|
| 25 |
end |
|
| 26 |
# authenticate user |
|
| 27 |
return nil unless identity.authenticateWithPassword(password) |
|
| 28 |
# filter users |
|
| 29 |
return nil unless self.attr_login.empty? || identity.isMemberOfGroup(group_for_name!(self.attr_login, authority)) |
|
| 30 |
# return attributes |
|
| 31 |
attrs |
|
| 32 |
end |
|
| 33 | ||
| 34 |
def auth_method_name |
|
| 35 |
"Identity Services" |
|
| 36 |
end |
|
| 37 | ||
| 38 |
def test_connection |
|
| 39 |
authority = authority_for_name!(self.host) |
|
| 40 |
# also check filtered group |
|
| 41 |
group_for_name!(self.attr_login, authority) unless self.attr_login.empty? |
|
| 42 |
end |
|
| 43 |
|
|
| 44 |
private |
|
| 45 | ||
| 46 |
def group_for_name!(group_name, authority) |
|
| 47 |
group = group_for_name(group_name, authority) |
|
| 48 |
raise "Identity Services Group \"#{group_name}\" not found" if group.nil?
|
|
| 49 |
group |
|
| 50 |
end |
|
| 51 | ||
| 52 |
def group_for_name(group_name, authority) |
|
| 53 |
group = OSX::CBIdentity.identityWithName_authority(group_name, authority) |
|
| 54 |
group.is_a?(OSX::CBGroupIdentity) ? group : nil |
|
| 55 |
end |
|
| 56 |
|
|
| 57 |
def authority_for_name!(auth_name) |
|
| 58 |
authority = authority_for_name(auth_name) |
|
| 59 |
raise "Identity Services Authority \"#{auth_name}\" not found" if authority.nil?
|
|
| 60 |
authority |
|
| 61 |
end |
|
| 62 |
|
|
| 63 |
def authority_for_name(auth_name) |
|
| 64 |
case auth_name |
|
| 65 |
when OSX::CBIdentityAuthority.defaultIdentityAuthority.localizedName: OSX::CBIdentityAuthority.defaultIdentityAuthority |
|
| 66 |
when OSX::CBIdentityAuthority.managedIdentityAuthority.localizedName: OSX::CBIdentityAuthority.managedIdentityAuthority |
|
| 67 |
when OSX::CBIdentityAuthority.localIdentityAuthority.localizedName: OSX::CBIdentityAuthority.localIdentityAuthority |
|
| 68 |
else nil |
|
| 69 |
end |
|
| 70 |
end |
|
| 71 |
|
|
| 72 |
def fullname_for_identity(identity) |
|
| 73 |
fullname = identity.fullName |
|
| 74 |
fullname && fullname.to_ruby |
|
| 75 |
end |
|
| 76 |
|
|
| 77 |
def mail_for_identity(identity) |
|
| 78 |
email = identity.emailAddress |
|
| 79 |
return nil if email.nil? |
|
| 80 |
preferred_email = email.to_ruby |
|
| 81 |
if self.tls # try for local address if possible |
|
| 82 |
user_aliases = identity.aliases.to_ruby |
|
| 83 |
host_parts = Socket.gethostname.split(".")
|
|
| 84 |
while host_parts.size > 1 |
|
| 85 |
suffix = "@" + host_parts.join(".")
|
|
| 86 |
user_aliases.each do |a| |
|
| 87 |
if a[-suffix.length, suffix.length] == suffix |
|
| 88 |
preferred_email = a |
|
| 89 |
break |
|
| 90 |
end |
|
| 91 |
end |
|
| 92 |
host_parts.delete_at(0) |
|
| 93 |
end |
|
| 94 |
end |
|
| 95 |
preferred_email |
|
| 96 |
end |
|
| 97 | ||
| 98 |
def part_is_suffix(part) |
|
| 99 |
@@suffixes ||= [ "JR.", "SR.", "JR", "SR", "II", "III", "3RD","IV", "V", "VI", "VII" ] |
|
| 100 |
@@suffixes.include?(part.upcase) |
|
| 101 |
end |
|
| 102 | ||
| 103 |
def part_is_lastname(part) |
|
| 104 |
@@lastnames ||= [ "DE", "DER", "DI", "LA", "LE", "MAC", "MC", "VAN", "VON", "PONCE" ] |
|
| 105 |
@@lastnames.include?(part.upcase) |
|
| 106 |
end |
|
| 107 | ||
| 108 |
def fullname_to_first_last(fullname) |
|
| 109 |
return [nil, nil] if fullname.nil? |
|
| 110 |
|
|
| 111 |
suffix = nil |
|
| 112 |
firstname = "" |
|
| 113 |
lastname = "" |
|
| 114 |
|
|
| 115 |
# split on comma |
|
| 116 |
parts = fullname.split(",").map { |i| i.strip }
|
|
| 117 |
|
|
| 118 |
# got at least one comma - check for suffix |
|
| 119 |
if parts.size > 1 && part_is_suffix(parts[-1]) |
|
| 120 |
suffix = ", " + parts.slice!(-1) |
|
| 121 |
end |
|
| 122 | ||
| 123 |
if parts.size == 1 |
|
| 124 |
firstparts = [] |
|
| 125 |
lastparts = [] |
|
| 126 |
# need to determine where first and last name splits |
|
| 127 |
parts = parts.first.split(" ")
|
|
| 128 |
# take care of suffix (if any) |
|
| 129 |
if parts.size > 1 && part_is_suffix(parts[-1]) |
|
| 130 |
suffix = " " + parts.slice!(-1) |
|
| 131 |
end |
|
| 132 |
# last name |
|
| 133 |
if parts.size > 0 |
|
| 134 |
lastparts.unshift(parts.slice!(-1)) |
|
| 135 |
end |
|
| 136 |
# first name |
|
| 137 |
if parts.size > 0 |
|
| 138 |
firstparts.push(parts.slice!(0)) |
|
| 139 |
end |
|
| 140 |
# all the rest |
|
| 141 |
while parts.size > 0 && part_is_lastname(parts.slice(-1)) |
|
| 142 |
lastparts.unshift(parts.slice!(-1)) |
|
| 143 |
end |
|
| 144 |
# make strings |
|
| 145 |
firstparts.push(parts) if parts.size > 0 |
|
| 146 |
firstname = firstparts.join(" ")
|
|
| 147 |
firstname += suffix unless suffix.nil? |
|
| 148 |
lastname = lastparts.join(" ")
|
|
| 149 |
elsif parts.size > 1 |
|
| 150 |
# now have last, first |
|
| 151 |
lastname = parts.slice!(0) |
|
| 152 |
firstname = parts.join(", ")
|
|
| 153 |
firstname += suffix unless suffix.nil? |
|
| 154 |
end |
|
| 155 |
[firstname, lastname] |
|
| 156 |
end |
|
| 157 |
|
|
| 158 |
end |
|
| app/controllers/auth_sources_controller.rb (working copy) | ||
|---|---|---|
| 17 | 17 | |
| 18 | 18 |
class AuthSourcesController < ApplicationController |
| 19 | 19 |
before_filter :require_admin |
| 20 |
helper :auth_sources_ids |
|
| 20 | 21 | |
| 21 | 22 |
def index |
| 22 | 23 |
list |
| ... | ... | |
| 33 | 34 |
end |
| 34 | 35 | |
| 35 | 36 |
def new |
| 36 |
@auth_source = AuthSourceLdap.new |
|
| 37 |
@auth_source = case params[:type] # parameter passed in url |
|
| 38 |
when "AuthSourceIds": AuthSourceIds.new |
|
| 39 |
else AuthSourceLdap.new |
|
| 40 |
end |
|
| 37 | 41 |
end |
| 38 | 42 | |
| 39 | 43 |
def create |
| 40 |
@auth_source = AuthSourceLdap.new(params[:auth_source]) |
|
| 44 |
@auth_source = case params[:auth_source][:type] |
|
| 45 |
when "AuthSourceIds": AuthSourceIds.new(params[:auth_source]) |
|
| 46 |
else AuthSourceLdap.new(params[:auth_source]) |
|
| 47 |
end |
|
| 41 | 48 |
if @auth_source.save |
| 42 | 49 |
flash[:notice] = l(:notice_successful_create) |
| 43 | 50 |
redirect_to :action => 'list' |
| app/views/auth_sources/list.rhtml (working copy) | ||
|---|---|---|
| 1 | 1 |
<div class="contextual"> |
| 2 |
<%= link_to l(:label_auth_source_new), {:action => 'new'}, :class => 'icon icon-add' %>
|
|
| 2 |
<%= link_to l(:label_auth_source_new_ldap), {:action => 'new'}, :class => 'icon icon-add' %>
|
|
| 3 |
<%= link_to l(:label_auth_source_new_ids), {:action => 'new', :type => 'AuthSourceIds'}, :class => 'icon icon-add' %>
|
|
| 3 | 4 |
</div> |
| 4 | 5 | |
| 5 | 6 |
<h2><%=l(:label_auth_source_plural)%></h2> |
| ... | ... | |
| 8 | 9 |
<thead><tr> |
| 9 | 10 |
<th><%=l(:field_name)%></th> |
| 10 | 11 |
<th><%=l(:field_type)%></th> |
| 11 |
<th><%=l(:field_host)%></th> |
|
| 12 |
<th><%=l(:field_host_authority)%></th>
|
|
| 12 | 13 |
<th></th> |
| 13 | 14 |
<th></th> |
| 14 | 15 |
</tr></thead> |
| app/views/auth_sources/_form.rhtml (working copy) | ||
|---|---|---|
| 1 |
<%= error_messages_for 'auth_source' %> |
|
| 2 | ||
| 3 |
<div class="box"> |
|
| 4 |
<!--[form:auth_source]--> |
|
| 5 |
<p><label for="auth_source_name"><%=l(:field_name)%> <span class="required">*</span></label> |
|
| 6 |
<%= text_field 'auth_source', 'name' %></p> |
|
| 7 | ||
| 8 |
<p><label for="auth_source_host"><%=l(:field_host)%> <span class="required">*</span></label> |
|
| 9 |
<%= text_field 'auth_source', 'host' %></p> |
|
| 10 | ||
| 11 |
<p><label for="auth_source_port"><%=l(:field_port)%> <span class="required">*</span></label> |
|
| 12 |
<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS</p> |
|
| 13 | ||
| 14 |
<p><label for="auth_source_account"><%=l(:field_account)%></label> |
|
| 15 |
<%= text_field 'auth_source', 'account' %></p> |
|
| 16 | ||
| 17 |
<p><label for="auth_source_account_password"><%=l(:field_password)%></label> |
|
| 18 |
<%= password_field 'auth_source', 'account_password', :name => 'ignore', |
|
| 19 |
:value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)),
|
|
| 20 |
:onfocus => "this.value=''; this.name='auth_source[account_password]';", |
|
| 21 |
:onchange => "this.name='auth_source[account_password]';" %></p> |
|
| 22 | ||
| 23 |
<p><label for="auth_source_base_dn"><%=l(:field_base_dn)%> <span class="required">*</span></label> |
|
| 24 |
<%= text_field 'auth_source', 'base_dn', :size => 60 %></p> |
|
| 25 | ||
| 26 |
<p><label for="auth_source_onthefly_register"><%=l(:field_onthefly)%></label> |
|
| 27 |
<%= check_box 'auth_source', 'onthefly_register' %></p> |
|
| 28 |
</div> |
|
| 29 | ||
| 30 |
<fieldset class="box"><legend><%=l(:label_attribute_plural)%></legend> |
|
| 31 |
<p><label for="auth_source_attr_login"><%=l(:field_login)%> <span class="required">*</span></label> |
|
| 32 |
<%= text_field 'auth_source', 'attr_login', :size => 20 %></p> |
|
| 33 | ||
| 34 |
<p><label for="auth_source_attr_firstname"><%=l(:field_firstname)%></label> |
|
| 35 |
<%= text_field 'auth_source', 'attr_firstname', :size => 20 %></p> |
|
| 36 | ||
| 37 |
<p><label for="auth_source_attr_lastname"><%=l(:field_lastname)%></label> |
|
| 38 |
<%= text_field 'auth_source', 'attr_lastname', :size => 20 %></p> |
|
| 39 | ||
| 40 |
<p><label for="auth_source_attr_mail"><%=l(:field_mail)%></label> |
|
| 41 |
<%= text_field 'auth_source', 'attr_mail', :size => 20 %></p> |
|
| 42 |
</fieldset> |
|
| 43 |
<!--[eoform:auth_source]--> |
|
| 44 | ||
| app/views/auth_sources/_form_AuthSourceIds.rhtml (revision 0) | ||
|---|---|---|
| 1 |
<%= error_messages_for 'auth_source' %> |
|
| 2 | ||
| 3 |
<div class="box"> |
|
| 4 |
<!--[form:auth_source]--> |
|
| 5 |
<%= hidden_field 'auth_source', 'type' %> |
|
| 6 |
<p><label for="auth_source_name"><%=l(:field_name)%> <span class="required">*</span></label> |
|
| 7 |
<%= text_field 'auth_source', 'name' %></p> |
|
| 8 | ||
| 9 |
<p><label for="auth_source_host"><%=l(:field_authority)%> <span class="required">*</span></label> |
|
| 10 |
<%= ids_authority_select(@auth_source.host) %></p> |
|
| 11 | ||
| 12 |
<p><label for="auth_source_onthefly_register"><%=l(:field_onthefly)%></label> |
|
| 13 |
<%= check_box 'auth_source', 'onthefly_register' %></p> |
|
| 14 |
</div> |
|
| 15 | ||
| 16 |
<fieldset class="box"><legend><%=l(:label_options)%></legend> |
|
| 17 |
<p><label for="auth_source_tls"><%=l(:field_prefer_local_email)%></label> |
|
| 18 |
<%= check_box 'auth_source', 'tls' %></p> |
|
| 19 | ||
| 20 |
<p><label for="auth_source_attr_login"><%=l(:field_restrict_user_group)%></label> |
|
| 21 |
<%= text_field 'auth_source', 'attr_login', :size => 30 %></p> |
|
| 22 | ||
| 23 |
</fieldset> |
|
| 24 | ||
| 25 |
<!--[eoform:auth_source]--> |
|
| 26 | ||
| app/views/auth_sources/edit.rhtml (working copy) | ||
|---|---|---|
| 1 | 1 |
<h2><%=l(:label_auth_source)%> (<%= @auth_source.auth_method_name %>)</h2> |
| 2 | 2 | |
| 3 | 3 |
<% form_tag({:action => 'update', :id => @auth_source}, :class => "tabular") do %>
|
| 4 |
<%= render :partial => 'form' %>
|
|
| 4 |
<%= render :partial => 'form_' + @auth_source.class.to_s %>
|
|
| 5 | 5 |
<%= submit_tag l(:button_save) %> |
| 6 | 6 |
<% end %> |
| 7 | 7 | |
| app/views/auth_sources/_form_AuthSourceLdap.rhtml (working copy) | ||
|---|---|---|
| 2 | 2 | |
| 3 | 3 |
<div class="box"> |
| 4 | 4 |
<!--[form:auth_source]--> |
| 5 |
<%= hidden_field 'auth_source', 'type' %> |
|
| 5 | 6 |
<p><label for="auth_source_name"><%=l(:field_name)%> <span class="required">*</span></label> |
| 6 | 7 |
<%= text_field 'auth_source', 'name' %></p> |
| 7 | 8 | |
| app/views/auth_sources/new.rhtml (working copy) | ||
|---|---|---|
| 1 | 1 |
<h2><%=l(:label_auth_source_new)%> (<%= @auth_source.auth_method_name %>)</h2> |
| 2 | 2 | |
| 3 | 3 |
<% form_tag({:action => 'create'}, :class => "tabular") do %>
|
| 4 |
<%= render :partial => 'form' %>
|
|
| 4 |
<%= render :partial => 'form_' + @auth_source.class.to_s %>
|
|
| 5 | 5 |
<%= submit_tag l(:button_create) %> |
| 6 | 6 |
<% end %> |
| lang/en.yml (working copy) | ||
|---|---|---|
| 152 | 152 |
field_version: Version |
| 153 | 153 |
field_type: Type |
| 154 | 154 |
field_host: Host |
| 155 |
field_host_authority: Host/Authority |
|
| 156 |
field_authority: Authority |
|
| 155 | 157 |
field_port: Port |
| 156 | 158 |
field_account: Account |
| 157 | 159 |
field_base_dn: Base DN |
| ... | ... | |
| 160 | 162 |
field_attr_lastname: Lastname attribute |
| 161 | 163 |
field_attr_mail: Email attribute |
| 162 | 164 |
field_onthefly: On-the-fly user creation |
| 165 |
field_prefer_local_email: Prefer local email |
|
| 166 |
field_restrict_user_group: Restrict Users to Group |
|
| 163 | 167 |
field_start_date: Start |
| 164 | 168 |
field_done_ratio: %% Done |
| 165 |
field_auth_source: Authentication mode
|
|
| 169 |
field_auth_source: Authentication source
|
|
| 166 | 170 |
field_hide_mail: Hide my email address |
| 167 | 171 |
field_comments: Comment |
| 168 | 172 |
field_url: URL |
| ... | ... | |
| 297 | 301 |
label_logged_as: Logged in as |
| 298 | 302 |
label_environment: Environment |
| 299 | 303 |
label_authentication: Authentication |
| 300 |
label_auth_source: Authentication mode |
|
| 301 |
label_auth_source_new: New authentication mode |
|
| 302 |
label_auth_source_plural: Authentication modes |
|
| 304 |
label_auth_source: Authentication source |
|
| 305 |
label_auth_source_new: New authentication source |
|
| 306 |
label_auth_source_new_ldap: New LDAP authentication source |
|
| 307 |
label_auth_source_new_ids: New Identity Services authentication source |
|
| 308 |
label_auth_source_plural: Authentication sources |
|
| 303 | 309 |
label_subproject_plural: Subprojects |
| 304 | 310 |
label_and_its_subprojects: %s and its subprojects |
| 305 | 311 |
label_min_max_length: Min - Max length |
| ... | ... | |
| 516 | 522 |
label_more: More |
| 517 | 523 |
label_scm: SCM |
| 518 | 524 |
label_plugins: Plugins |
| 519 |
label_ldap_authentication: LDAP authentication
|
|
| 525 |
label_ldap_authentication: External authentication
|
|
| 520 | 526 |
label_downloads_abbr: D/L |
| 521 | 527 |
label_optional_description: Optional description |
| 522 | 528 |
label_add_another_file: Add another file |