]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 241903: Add Environment Variable Authentication for apache auth and SSO
authorbugreport%peshkin.net <>
Wed, 11 Aug 2004 20:53:43 +0000 (20:53 +0000)
committerbugreport%peshkin.net <>
Wed, 11 Aug 2004 20:53:43 +0000 (20:53 +0000)
patch by erik
r=joel
a=justdave

Bugzilla/Auth/Login/WWW.pm
Bugzilla/Auth/Login/WWW/CGI.pm
Bugzilla/Auth/Login/WWW/Env.pm [new file with mode: 0644]
Bugzilla/User.pm
checksetup.pl
defparams.pl
template/en/default/global/useful-links.html.tmpl
template/en/default/index.html.tmpl
template/en/default/sidebar.xul.tmpl

index def68df63f87e848ee6febc01f5ecc37731bd284..8b7724befc2446b9fc35a251a4caa008e26cfb84 100644 (file)
@@ -36,6 +36,12 @@ sub login_class {
     return $current_login_class;
 }
 
+# can_logout determines if a user may log out
+sub can_logout {
+    return 1 if (login_class && login_class->can_logout);
+    return 0;
+}
+
 sub login {
     my ($class, $type) = @_;
 
@@ -64,6 +70,8 @@ sub login {
     if ($userid) {
         $user = new Bugzilla::User($userid);
 
+        $user->set_flags('can_logout' => $class->can_logout);
+
         # Compat stuff
         $::userid = $userid;
     } else {
@@ -74,7 +82,7 @@ sub login {
 
 sub logout {
     my ($class, $user, $option) = @_;
-    if ($class->login_class) {
+    if (can_logout) {
         $class->login_class->logout($user, $option);
     }
 }
index fb00cd01831c2095a9132ca7110ea9e8b6cfb0c7..3b90ec6adf4a64b03c6e186d36c57b2b987a6aa2 100644 (file)
@@ -186,6 +186,9 @@ sub login {
                                           type => $type, });
 }
 
+# This auth style allows the user to log out.
+sub can_logout { return 1; }
+
 # Logs user out, according to the option provided; this consists of
 # removing entries from logincookies for the specified $user.
 sub logout {
diff --git a/Bugzilla/Auth/Login/WWW/Env.pm b/Bugzilla/Auth/Login/WWW/Env.pm
new file mode 100644 (file)
index 0000000..abd1763
--- /dev/null
@@ -0,0 +1,182 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# implied. See the License for the specific language governing
+# rights and limitations under the License.
+#
+# The Original Code is the Bugzilla Bug Tracking System.
+#
+# The Initial Developer of the Original Code is Netscape Communications
+# Corporation. Portions created by Netscape are
+# Copyright (C) 1998 Netscape Communications Corporation. All
+# Rights Reserved.
+#
+# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
+
+package Bugzilla::Auth::Login::WWW::Env;
+
+use strict;
+
+use Bugzilla::Config;
+use Bugzilla::Error;
+use Bugzilla::Util;
+
+sub login {
+    my ($class, $type) = @_;
+
+    # XXX This does not currently work correctly with Param('requirelogin').
+    #     Bug 253636 will hopefully see that param's needs taken care of in a
+    #     parent module, but for the time being, this module does not honor
+    #     the param in the way that CGI.pm does.
+
+    my $matched_userid    = '';
+    my $matched_extern_id = '';
+    my $disabledtext      = '';
+
+    my $dbh = Bugzilla->dbh;
+    my $sth;
+
+    # Gather the environment variables
+    my $env_id       = $ENV{Param("auth_env_id")};
+    my $env_email    = $ENV{Param("auth_env_email")};
+    my $env_realname = $ENV{Param("auth_env_realname")};
+
+    # allow undefined values to work with trick_taint
+    for ($env_id, $env_email, $env_realname) { $_ ||= '' };
+    # make sure the email field contains only a valid email address
+    my $emailregexp = Param("emailregexp");
+    $env_email =~ /($emailregexp)/;
+    $env_email = $1;
+    # untaint the remaining values
+    trick_taint($env_id);
+    trick_taint($env_realname);
+
+    if ($env_id | $env_email) {
+        # Look in the DB for the extern_id
+        if ($env_id) {
+
+            # Not having the email address defined but having an ID isn't
+            # allowed.
+            return undef unless $env_email;
+
+            $sth = $dbh->prepare("SELECT userid, disabledtext " .
+                                 "FROM profiles WHERE extern_id=?");
+            $sth->execute($env_id);
+            my $fetched = $sth->fetch;
+            if ($fetched) {
+                $matched_userid = $fetched->[0];
+                $disabledtext   = $fetched->[1];
+            }
+        }
+
+        unless ($matched_userid) {
+            # There was either no match for the external ID given, or one was
+            # not present.
+            #
+            # Check to see if the email address is in there and has no
+            # external id assigned.  We test for both the login name (which we
+            # also sent), and the id, so that we have a way of telling that we
+            # got something instead of a bunch of NULLs
+            $sth = $dbh->prepare("SELECT extern_id, userid, disabledtext " .
+                                 "FROM profiles WHERE login_name=?");
+            $sth->execute($env_email);
+
+            $sth->execute();
+            my $fetched = $sth->fetch();
+            if ($fetched) {
+                ($matched_extern_id, $matched_userid, $disabledtext) = @{$fetched};
+            }
+            if ($matched_userid) {
+                if ($matched_extern_id) {
+                    # someone with a different external ID has that address!
+                    ThrowUserError("extern_id_conflict");
+                }
+                else
+                {
+                    # someone with no external ID used that address, time to
+                    # add the ID!
+                    $sth = $dbh->prepare("UPDATE profiles " .
+                                         "SET extern_id=? WHERE userid=?");
+                    $sth->execute($env_id, $matched_userid);
+                }
+            }
+            else
+            {
+                # Need to create a new user with that email address.  Note
+                # that cryptpassword has been filled in with '*', since the
+                # user has no DB password.
+                $sth = $dbh->prepare("INSERT INTO profiles ( " .
+                                     "login_name, cryptpassword, " .
+                                     "realname, disabledtext " .
+                                     ") VALUES ( ?, ?, ?, '' )");
+                $sth->execute($env_email, '*', $env_realname);
+                $sth = $dbh->prepare("SELECT last_insert_id()");
+                $sth->execute();
+                $matched_userid = $sth->fetch->[0];
+            }
+        }
+    }
+
+    # now that we hopefully have a username, we need to see if the data
+    # has to be updated
+    if ($matched_userid) {
+        $sth = $dbh->prepare("SELECT login_name, realname " .
+                             "FROM profiles " .
+                             "WHERE userid=?");
+        $sth->execute($matched_userid);
+        my $fetched = $sth->fetch;
+        my $username = $fetched->[0];
+        my $this_realname = $fetched->[1];
+        if ( ($username ne $env_email) ||
+             ($this_realname ne $env_realname) ) {
+
+            $sth = $dbh->prepare("UPDATE profiles " .
+                                 "SET login_name=?, " .
+                                 "realname=? " .
+                                 "WHERE userid=?");
+            $sth->execute($env_email,
+                          ($env_realname || $this_realname),
+                          $matched_userid);
+            $sth->execute;
+        }
+    }
+
+    # Now we throw an error if the user has been disabled
+    if ($disabledtext) {
+        ThrowUserError("account_disabled",
+                       {'disabled_reason' => $disabledtext});
+    }
+
+    return $matched_userid;
+
+}
+
+# This auth style does not allow the user to log out.
+sub can_logout { return 0; }
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Auth::Env - Environment Variable Authentication
+
+=head1 DESCRIPTION
+
+Many external user authentication systems supply login information to CGI
+programs via environment variables.  This module checks to see if those
+variables are populated and, if so, assumes authentication was successful and
+returns the user's ID, having automatically created a new profile if
+necessary.
+
+=head1 SEE ALSO
+
+L<Bugzilla::Auth>
+
index a404500764eac36fed16c00fe08246b1fee21cc5..38453841b145d24140499eed105aa8b1d0282f91 100644 (file)
@@ -79,6 +79,7 @@ sub _create {
         'name'           => '',
         'login'          => '',
         'showmybugslink' => 0,
+        'flags'          => {},
     };
     bless ($self, $class);
     return $self unless $cond && $val;
@@ -139,6 +140,19 @@ sub email { $_[0]->{login}; }
 sub name { $_[0]->{name}; }
 sub showmybugslink { $_[0]->{showmybugslink}; }
 
+sub set_flags {
+    my $self = shift;
+    while (my $key = shift) {
+        $self->{'flags'}->{$key} = shift;
+    }
+}
+
+sub get_flag {
+    my $self = shift;
+    my $key = shift;
+    return $self->{'flags'}->{$key};
+}
+
 # Generate a string to identify the user by name + email if the user
 # has a name or by email only if she doesn't.
 sub identity {
@@ -1109,6 +1123,21 @@ all MySQL supported, this will go away.
 
 Returns C<1> if the user can bless at least one group. Otherwise returns C<0>.
 
+=item C<set_flags>
+=item C<get_flag>
+
+User flags are template-accessible user status information, stored in the form
+of a hash.  For an example of use, when the current user is authenticated in
+such a way that they are allowed to log out, the 'can_logout' flag is set to
+true (1).  The template then checks this flag before displaying the "Log Out"
+link.
+
+C<set_flags> is called with any number of key,value pairs.  Flags for each key
+will be set to the specified value.
+
+C<get_flag> is called with a single key name, which returns the associated
+value.
+
 =back
 
 =head1 SEE ALSO
index c42c23535c00d8e64e785efd6f452e1811051694..703358cec096cd5b42d2ddb5c28268ece9150fd9 100755 (executable)
@@ -1810,6 +1810,7 @@ $table{profiles} =
     mybugslink tinyint not null default 1,
     emailflags mediumtext,
     refreshed_when datetime not null,
+    extern_id varchar(64) default null,
     unique(login_name)';
 
 
@@ -3999,6 +4000,10 @@ if (GetFieldDef("group_group_map", "isbless")) {
               ADD UNIQUE (member_id, grantor_id, grant_type)");
 }    
 
+# Allow profiles to optionally be linked to a unique identifier in an outside
+# login data source
+AddField("profiles", "extern_id", "varchar(64)");
+
 # If you had to change the --TABLE-- definition in any way, then add your
 # differential change code *** A B O V E *** this comment.
 #
index 86cc7f2d5d5bc1b4c6e986b482f42c61ca2e7b04..8260be9784dae9b6d69508659e29e6c72513a03c 100644 (file)
@@ -435,6 +435,36 @@ sub find_languages {
    default => '',
   },
 
+  {
+   name => 'auth_env_id',
+   desc    => 'Environment variable used by external authentication system ' .
+              'to store a unique identifier for each user.  Leave it blank ' .
+              'if there isn\'t one or if this method of authentication ' .
+              'is not being used.',
+   type    => 't',
+   default => '',
+  },
+
+  {
+   name    => 'auth_env_email',
+   desc    => 'Environment variable used by external authentication system ' .
+              'to store each user\'s email address.  This is a required ' .
+              'field for environmental authentication.  Leave it blank ' .
+              'if you are not going to use this feature.',
+   type    => 't',
+   default => '',
+  },
+
+  {
+   name    => 'auth_env_realname',
+   desc    => 'Environment variable used by external authentication system ' .
+              'to store the user\'s real name.  Leave it blank if there ' .
+              'isn\'t one or if this method of authentication is not being ' .
+              'used.',
+   type    => 't',
+   default => '',
+  },
+
   # XXX in the future:
   #
   # user_verify_class and user_info_class should have choices gathered from
@@ -455,9 +485,14 @@ sub find_languages {
               <dd>
                 Asks for username and password via CGI form interface.
               </dd>
-             </dl>',
+              <dt>Env</dt>
+              <dd>
+                Info for a pre-authenticated user is passed in system
+                environment variables.
+              </dd>
+            </dl>',
    type => 's',
-   choices => [ 'CGI' ],
+   choices => [ 'CGI', 'Env', 'Env,CGI' ],
    default => 'CGI',
    checker => \&check_multi
   },
index c04b60dd6c96ea6d3f1eef66826d6832976b2669..7e568372c905d7e05e56d317964c041557b97f7f 100644 (file)
         [% IF user.login %] 
             [% ' | <a href="sanitycheck.cgi">Sanity&nbsp;check</a>' 
                                                   IF user.groups.tweakparams %]
-            | <a href="relogin.cgi">Log&nbsp;out</a>&nbsp;
-              [% user.login FILTER html %]
+            [% IF user.get_flag('can_logout') %]
+              | <a href="relogin.cgi">Log&nbsp;out</a>&nbsp;
+            [% ELSE %]
+              | Logged&nbsp;in&nbsp;as&nbsp;
+            [% END %]
+            [% user.login FILTER html %]
         [% ELSE %]
             [% IF Param('createemailregexp') %]
               | <a href="createaccount.cgi">New&nbsp;Account</a>
index 5422061fdd1e3623be666f3d26177545846de07a..d31a51d89abef8f0381b38cd891e1555df592c79 100644 (file)
@@ -66,7 +66,9 @@ function addSidebar() {
   </p><p>
 [% IF user.id %]
   <a href="userprefs.cgi">Change password or user preferences</a><br>
-  <a href="relogin.cgi">Logout [% user.login FILTER html %]</a><br>
+  [% IF user.get_flag('can_logout') %]
+    <a href="relogin.cgi">Logout [% user.login FILTER html %]</a><br>
+  [% END %]
 [% ELSE %]
   <a href="query.cgi?GoAheadAndLogIn=1">Log in to an existing account</a><br>
   [% IF Param('createemailregexp') %]
index 60ae33dbd1200d4ca0ef9f8df991ff6b93afa30f..802fd9f23a002bf1cab99470ad5c36b0a6c8f21d 100644 (file)
@@ -95,7 +95,9 @@ function normal_keypress_handler( aEvent ) {
   [%- IF user.groups.tweakparams %]
       <text class="text-link" onclick="load_relative_url('sanitycheck.cgi')" value="sanity check"/>
   [%- END %]
+  [%- IF user.get_flag('can_logout') %]
       <text class="text-link" onclick="load_relative_url('relogin.cgi')" value="logout [% user.login FILTER html %]"/>
+  [%- END %]
       <separator class="thin"/>
   [%- IF user.showmybugslink %]
       [% filtered_username = user.login FILTER url_quote %]