Project

General

Profile

Mac OS X Identity Services Authentication Hack ยป redmine_identity_services_hack.diff

Brian Wells, 2008-09-26 04:39

View differences:

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
    (1-1/1)