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 |