1 |
Title: Writing a Custom Login/Authentication Handler |
Title: Writing a Custom Login/Authentication Handler |
2 |
Status: Obsolete |
Status: Current |
3 |
|
Revised: 18-SEP-2002 |
4 |
|
|
5 |
|
|
6 |
Introduction |
Introduction |
7 |
------------ |
------------ |
8 |
|
|
9 |
By default, GNUe clients look at a database connection, determine what |
By default, GNUe clients look at a database connection, determine what |
10 |
fields it needs in order to login (e.g., username and password), and then |
fields it needs in order to login (e.g., username and password), and then |
11 |
asks its platform/interface dependent login handler to prompt the user for |
asks its platform/interface dependent login handler to prompt the user for |
12 |
this information. Once returned, the client connects to the database using |
this information. Once returned, the client connects to the database using |
13 |
this information. |
this information. |
14 |
|
|
15 |
If needed, you can intercept a client's normal login handler to add your |
If needed, you can intercept a client's normal login handler to add your |
16 |
own behavior. |
own behavior. |
17 |
|
|
18 |
|
|
19 |
Why? |
Why? |
20 |
---- |
---- |
21 |
Sometimes it is not enough to prompt for the "database" login. Perhaps |
Sometimes it is not enough to prompt for the "database" login. Perhaps |
22 |
you want finer control over logins, or simply need to authenticate against |
you want finer control over logins, or simply need to authenticate against |
23 |
something besides the database. |
something besides the database. |
24 |
|
|
25 |
You might want to authenticate against an NIS source, LDAP, or some custom |
You might want to authenticate against an NIS source, LDAP, or some custom |
26 |
source. Maybe you have many users and want to authenticate against rows in |
source. Maybe you have many users and want to authenticate against rows in |
27 |
a database table and have the actual database login name/password be a |
a database table and have the actual database login name/password be a |
28 |
common one that no one knows; i.e., while users Jason and James might log |
common one that no one knows; i.e., while users Jason and James might log |
29 |
in using "jason" and "james", they might both connect to the database as |
in using "jason" and "james", they might both connect to the database as |
30 |
"commonuser". Since you may not trust James, you only want him to know |
"commonuser". Since you may not trust James, you only want him to know |
31 |
that he is logging in as "james" and never know that he is being connected |
that he is logging in as "james" and never know that he is being connected |
32 |
as "commonuser". |
as "commonuser". |
33 |
|
|
34 |
|
|
36 |
What do I do? |
What do I do? |
37 |
------------- |
------------- |
38 |
|
|
39 |
First, a little explanation of how logins work: |
First, a little explanation of how logins work: |
40 |
|
|
41 |
1. A client needs to initialize a connection to a database, so it passes |
1. A client needs to initialize a connection to a database, so it passes |
42 |
GNUe-Common a description of the database and asks for it to connect. |
GNUe-Common a description of the database and asks for it to connect. |
43 |
|
|
44 |
2. GNUe-Common looks at the database description, determines what values |
2. GNUe-Common looks at the database description, determines what values |
45 |
are needed to connect (usually, username and password). |
are needed to connect (usually, username and password). |
46 |
|
|
47 |
3. GNUe-Common creates an instance of (or uses an existing instance of) |
3. GNUe-Common creates an instance of (or uses an existing instance of) |
48 |
gnue.common.GLoginHander.LoginHandler, or if the client provides a |
gnue.common.GLoginHander.LoginHandler, or if the client provides a |
49 |
more advanced login handler (e.g., GNUe Form's graphical handler), an |
more advanced login handler (e.g., GNUe Form's graphical handler), an |
50 |
instance of this. |
instance of this. |
51 |
|
|
52 |
4. GNUe-Common calls LoginHandler.getLogin(), passing it basic information |
4. GNUe-Common calls LoginHandler.getLogin(), passing it basic information |
53 |
about the needed connection and a list of values that login handler |
about the needed connection and a list of values that login handler |
54 |
should provide (i.e., '_username' & '_password'). |
should provide (i.e., '_username' & '_password'). |
55 |
|
|
56 |
5. GNUe-Common uses the results of the call to LoginHandler.getLogin() to |
5. GNUe-Common uses the results of the call to LoginHandler.getLogin() to |
57 |
create a connection to the database. |
create a connection to the database. |
58 |
|
|
59 |
|
|
60 |
Now, if needed, you can subclass LoginHandler (basically intercepting |
Now, if needed, you can create an Authenticator class that basically |
61 |
steps #3-4 above) and provide your own functionality. All that is needed |
intervenes in between steps #3-4 above and provide your own functionality. |
|
is for you to provide class with a method called getLogin that takes as |
|
|
input a tuple of: |
|
|
|
|
|
(database id, description, (requiredValues) ) |
|
|
|
|
|
and return a hash containing a dictionary/hash: |
|
|
|
|
|
{ 'field': 'value' }. |
|
|
|
|
|
RequiredValues is a tuple of tuples containing: |
|
|
|
|
|
(value id, value description, isPassword) |
|
|
|
|
|
Example of a getLogin call as used by GNUe Common |
|
|
|
|
|
loginHandler.getLogin ('gnue', # Connection ID |
|
|
'GNUe Test Database', # Connection Name |
|
|
( ('_username','User Name', 0), # Required |
|
|
('_password','Password', 1) ) ) # Fields |
|
|
|
|
|
You would want to write your own handler as such: |
|
|
|
|
|
|
|
|
##################################################### |
|
|
# |
|
|
# Template for a custom login handler |
|
|
# |
|
|
|
|
|
# Import the base login handler; |
|
|
# Note: it is important that this line not change. |
|
|
from gnue.common.GLoginHander import LoginHandler |
|
|
|
|
|
# Do not rename this class. |
|
|
# GNUe clients expect to see a MyLoginHander class |
|
|
class MyLoginHandler(LoginHandler): |
|
|
|
|
|
# |
|
|
# getLogin is passed an list consisting of: |
|
|
# Connection Name |
|
|
# Connection Comments/Description |
|
|
# List of Fields to Input: |
|
|
# Attribute Name, Label/Description, Is Password? |
|
|
# |
|
|
# It should return a dictionary of {Attribute Name: Inputted Value} |
|
|
# |
|
|
def getLogin(self, loginData): |
|
|
|
|
|
connName = loginData[0] |
|
|
connDescription = loginData[1] |
|
|
requiredFields = loginData[2] |
|
|
|
|
|
# Perform any tasks prior to prompting for username/password. |
|
|
# At this point, you might want to add your needed fields to |
|
|
# requiredFields or set default values. Note: to set default |
|
|
# values, set 'self._defaults['fieldname'] = 'default' |
|
|
# (e.g., 'self._defaults['_username'] = os.env['LOGNAME'] ) |
|
|
|
|
|
loginFields = LoginHandler.getLogin (self, (connName, |
|
|
connDescription, |
|
|
requiredFields) ) |
|
|
|
|
|
# Perform any tasks prior to returning username/password |
|
|
# The entered username/password is stored in loginFields['_username'] |
|
|
# and loginFields['_password']. At this point |
|
|
|
|
|
return loginFields |
|
|
|
|
|
# |
|
|
##################################################### |
|
|
|
|
|
|
|
|
Save this file as a .py file and put it's location in your gnue.conf (change |
|
|
"loginHandler =" to "loginHandler = /path/to/myhandler.py" ) |
|
62 |
|
|
63 |
If all goes well, your login handler will be used the next time you log in. |
If all goes well, your login handler will be used the next time you log in. |
64 |
|
|
65 |
Some notes: |
Some notes: |
66 |
|
|
67 |
1) GNUe-Common will expect to see to two objects in your handler's |
1) GNUe-Common will expect to see to two objects in your handler's |
68 |
namespace: LoginHandler (from the import statement) and |
namespace: LoginHandler (from the import statement) and |
69 |
MyLoginHandler (your customized login class) |
MyLoginHandler (your customized login class) |
70 |
|
|
71 |
For this reason, do NOT change the import statement to read: |
For this reason, do NOT change the import statement to read: |
72 |
from gnue.common import GLoginHandler |
from gnue.common import GLoginHandler |
73 |
and then change the class' parent to be GLoginHandler.LoginHandler. |
and then change the class' parent to be GLoginHandler.LoginHandler. |
74 |
If you do, your customized handler will probably not work the way |
If you do, your customized handler will probably not work the way |
75 |
you expect. |
you expect. |
76 |
|
|
77 |
2) Almost the only restriction placed on getLogin's functionality is |
2) Almost the only restriction placed on getLogin's functionality is |
78 |
that it must return a hash containing at least the values requested |
that it must return a hash containing at least the values requested |
79 |
when getLogin was called, using the value id's supplied. Your code, |
when getLogin was called, using the value id's supplied. Your code, |
80 |
in theory, can do whatever it needs in order to return these values. |
in theory, can do whatever it needs in order to return these values. |
81 |
|
|
82 |
HOWEVER: Even though it is possible, it is NOT recommended that you |
HOWEVER: Even though it is possible, it is NOT recommended that you |
83 |
try to write your code to prompt for the values. It is HIGHLY |
try to write your code to prompt for the values. It is HIGHLY |
84 |
recommended that you call LoginHandler.getLogin (your superclass's |
recommended that you return values in the getLoginFields method so |
85 |
getLogin) to prompt for input. The superclass' LoginHandler will |
LoginHandler.getLogin can prompt for input. The LoginHandler will |
86 |
know how to handle the current user's environment (i.e., should it |
know how to handle the current user's environment (i.e., should it |
87 |
display a GTK login box, generate HTML for a login box, not display |
display a GTK login box, generate HTML for a login box, not display |
88 |
a box at all, but prompt using good ol' fashioned text prompts?) |
a box at all, but prompt using good ol' fashioned text prompts?) |
89 |
It is quite extensible. If you need to prompt for more fields than |
It is quite extensible. If you need to prompt for more fields than |
90 |
simply Username and Password, then simply add a definition to your |
simply Username and Password, then simply add a definition to your |
91 |
requiredFields tuple and let the superclass' getLogin prompt for you. |
requiredFields tuple and let the LoginHandler's getLogin prompt for |
92 |
|
you. |
93 |
|
|
94 |
|
|
95 |
|
There are three steps to adding a custom authenticator: |
96 |
|
|
97 |
|
1. Create (or copy) a Python file that implements an Authenticator. |
98 |
|
An Authenticator contains a class called Authenticator, which has |
99 |
|
two methods, getRequiredFields() and login(). See the example below |
100 |
|
for details on what's expected from these two methods. |
101 |
|
|
102 |
|
2. Place this file in either your Python search path, or in a |
103 |
|
path specified by ImportPath (in the [common] section of gnue.conf) |
104 |
|
|
105 |
|
3. In your connections.conf file, add a custom_auth parameter that |
106 |
|
is the name of the file (without the path or .py extension): |
107 |
|
|
108 |
|
[myconn] |
109 |
|
adapter = psycopg |
110 |
|
host = localhost |
111 |
|
dbname = mydb |
112 |
|
custom_auth = MyPostgresAuthenticator |
113 |
|
|
114 |
|
|
115 |
|
Creating a custom authenticator |
116 |
|
=============================== |
117 |
|
A slightly more complicated example. You have a table in one of your databases |
118 |
|
called "users", that has a user and password field. |
119 |
|
|
120 |
|
Your users will connect to the database using the username and password: |
121 |
|
dbUser and dbPassword. |
122 |
|
|
123 |
|
You, however, want them to use their own username and password to be |
124 |
|
authenticated, but after getting authenticated, the database only cares |
125 |
|
about the real "dbUser" and "dbPassword". |
126 |
|
|
127 |
|
File: MyPostgresAuthenticator.py |
128 |
|
=============================================================================== |
129 |
|
import psycopg |
130 |
|
from gnue.common.GConnections import LoginError |
131 |
|
|
132 |
|
class Authenticator: |
133 |
|
# |
134 |
|
# getLoginFields is passed an list consisting of: |
135 |
|
# Fields to Input: |
136 |
|
# Attribute Name, Label/Description, Is Password? |
137 |
|
# |
138 |
|
# This list is a list of the values the dbdriver |
139 |
|
# expects to get back in order to login. |
140 |
|
# |
141 |
|
# It should return a similarly formatted list that |
142 |
|
# tells the LoginHandler what values need to be |
143 |
|
# prompted for. (Typically, _username and _password) |
144 |
|
# If nothing should be prompted (e.g., you have a |
145 |
|
# certificate) then return () (an empty list). |
146 |
|
# |
147 |
|
def getLoginFields(self, dbRequiredFields): |
148 |
|
return dbRequiredFields |
149 |
|
|
150 |
|
# |
151 |
|
# Authenticate the user givem the values prompted for. |
152 |
|
# If the information is incorrect, then raise |
153 |
|
# gnue.common.GConnections.LoginError |
154 |
|
# |
155 |
|
# It should return a dictionary of {Attribute Name: Value} |
156 |
|
# |
157 |
|
def login(self, loginData): |
158 |
|
conn = psycopg.connect(user="theValidator", |
159 |
|
password="hotmomma", |
160 |
|
dbname="logins", |
161 |
|
host="localhost"') |
162 |
|
cursor = conn.cursor() |
163 |
|
results = cursor.execute ( |
164 |
|
'select 1 from users where username=%s and password=%s', |
165 |
|
(loginData['_username'], |
166 |
|
loginData['_password']) ) |
167 |
|
if not results.fetchone(): |
168 |
|
raise LoginError |
169 |
|
else: |
170 |
|
loginData['_username'] = 'dbLogin' |
171 |
|
loginData['_password'] = 'dbPassword' |
172 |
|
=============================================================================== |
173 |
|
|
174 |
|
Note: there are no hard and fast rules about what can go into the |
175 |
|
connections.conf file. If your authenticator needs more information |
176 |
|
stored in connections.conf, it is fine to do so. |
177 |
|
|
178 |
|
However, to avoid namespace collisions, you should probably prefix |
179 |
|
any custom connection.conf entries with common prefix. For example, |
180 |
|
if you are writing an NIS adapter, you should prefix entries in |
181 |
|
connections.conf like: |
182 |
|
|
183 |
|
auth_nis_domain = MYDOMAIN |
184 |
|
|
185 |
|
instead of something like: |
186 |
|
|
187 |
|
domain = MYDOMAIN |
188 |
|
|
189 |
|
More examples: |
190 |
|
|
191 |
|
auth_ldap_* |
192 |
|
auth_pg_* |
193 |
|
auth_mysql_* |
194 |
|
auth_kerberos_* |
195 |
|
auth_custom_* |
196 |
|
|
197 |
|
|
198 |
If you have written a customized login handler using backends not currently |
If you have written a customized login handler using backends not currently |
199 |
in our samples file (i.e., against NIS, Kerberos, LDAP, etc) and would like |
in our samples file (i.e., against NIS, Kerberos, LDAP, etc) and would like |
200 |
to donate your example for others to learn from, please email it to: |
to donate your example for others to learn from, please email it to: |
201 |
|
|
202 |
info@gnue.org |
info@gnue.org |
|
|
|
203 |
|
|