Authentication Poll & Community Design [PLEASE JOIN!]
Moderator: General Moderators
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
Anyway...
I'm going to be away for four weeks, starting Tuesday. This means that unless you guys take some initiative and write that code, we'll have to let this thing hibernate for a month. During that time, I'll be doodling. A lot. Hopefully, by the time I get back, I'll have a usable doodle to implement.
I'm going to be away for four weeks, starting Tuesday. This means that unless you guys take some initiative and write that code, we'll have to let this thing hibernate for a month. During that time, I'll be doodling. A lot. Hopefully, by the time I get back, I'll have a usable doodle to implement.
- RobertGonzalez
- Site Administrator
- Posts: 14293
- Joined: Tue Sep 09, 2003 6:04 pm
- Location: Fremont, CA, USA
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
But before I leave, I'm going to get some of the smaller components done. I've trimmed down the database layer: only a table gateway and a db wrapper.
I'm currently working on HashedPassword, an representation of a hashed salted password suitable for long term storage. It also is aware of the algorithm that was used to generate it, so it can mutate into a new hash if you change the algorithm!
I'm currently working on HashedPassword, an representation of a hashed salted password suitable for long term storage. It also is aware of the algorithm that was used to generate it, so it can mutate into a new hash if you change the algorithm!
What exactly are you working on?Ambush Commander wrote:But before I leave, I'm going to get some of the smaller components done. I've trimmed down the database layer: only a table gateway and a db wrapper.![]()
I'm currently working on HashedPassword, an representation of a hashed salted password suitable for long term storage. It also is aware of the algorithm that was used to generate it, so it can mutate into a new hash if you change the algorithm!
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
I've got some time tonight, so I'll be posting some of my musings.
But first, santosj, it's quite simple. White arrow = inherits/implements. Black arrow = object reference. All other stuff is secondary.
On Iterators: While reading the Gang of Four's Design Patterns, I finally got around to finding what exactly all this fuss about iterators is about. But I also realized something else:
In general, the iterator object is seperate from the data object, and you get an instance of the iterator using createIterator(). This is contrary to the SPL notion, where an object implements the Iterator interface and handles both data storage and iteration. Thus, while Gang of Four suggests two seperate objects, SPL meshes them together.
Also, it turns out that next() and reset()/first() form an even more simplified version of the iterator pattern, but they ARE iterators, nonetheless. This form maps most neatly to the MySQL functions. Alternatively:
There's also the question of what the most optimized practice is. I'm starting to wonder whether or not it really matters if we load the entire result set into memory, rather, the number of function calls and the number of passes over the data are the true performance influence? Hmm...
On coupling: I now fully recognize the need for each class to include necessary classes. This is because even though we can show inheritance hierarchies through naming, tight (and loose) coupling is not so evident.
On mutating the SaltedHash: We can put mutate($new_algorithm, $password) into SaltedHash because the behavior is encapsulated in another switchable object (the SaltedHash itself is quite generic). Sort of like the State pattern.
On Chain of Responsibility: I've been wondering how to best represent the various authentication actions: logout, login, resume session, remember me. Since each of these operations is mutually exclusive, as whether or not the other one executes is dependent on each of the previous action's success (or failure), Chain of Responsibility would appear to be quite a good way of implementing it. We just set up the chain between a bunch of command objects like LogoutCommand, LoginCommand, etc, passing $credentials and $command_context down to be acted upon appropriately.
While this seems like an attractive way of solving the problem, there lies the question: are all authentication actions truly mutually exclusive? The only other version I can think of is perhaps TwoFactorLogin or TemporaryPasswordLogin: my imagination is not that fertile. One could argue that if a command is executed, it would be much happier operating as a pre/post-authenticate hook, but...
On a different note, we could use the Composite pattern to further abstract the chain. Instead of each of these discrete operations, we instead have EndSessionCommand and StartSessionCommand, and then Login and friends get categorized accordingl. Perhaps this obviates the need for a Chain of Responsibility on the higher level.
An observation: as one precedes further down the chain, the authentication method is of decreasing security/sureness. A login is more conclusive about identity then a session command. And the same applies for Session versus Remember me.
Another important question: is it really desirable to allow a user to login/logout on every single page, or should the request be directed to a special login page? If the latter is implemented, then the user's application would have to find some good facility for redirecting the user back to their original area, which is quite difficult. But indeed it is much conceptually cleaner.
Finally, the ResumeSessionCommand may not belong in such a command chain. Because we are making our authentication system Session-oriented, this would be important enough to be executed every time. We end up resuming the session every time: logging in creates the session right before it is resumed.
Conclusion - Reading Design Patterns by GoF gives some very interesting concepts to apply in this authentication system to maximize extensibility. If you need explanations for any of the patterns (italicized for your convenience) please ask. If Devnetwork is blocked by China's firewall, this might be my last post for two weeks.
But first, santosj, it's quite simple. White arrow = inherits/implements. Black arrow = object reference. All other stuff is secondary.
On Iterators: While reading the Gang of Four's Design Patterns, I finally got around to finding what exactly all this fuss about iterators is about. But I also realized something else:
In general, the iterator object is seperate from the data object, and you get an instance of the iterator using createIterator(). This is contrary to the SPL notion, where an object implements the Iterator interface and handles both data storage and iteration. Thus, while Gang of Four suggests two seperate objects, SPL meshes them together.
Also, it turns out that next() and reset()/first() form an even more simplified version of the iterator pattern, but they ARE iterators, nonetheless. This form maps most neatly to the MySQL functions. Alternatively:
Code: Select all
for($i->first(); !$i->isDone(); $i->next()) {
// operate on $i->currentItem();
}On coupling: I now fully recognize the need for each class to include necessary classes. This is because even though we can show inheritance hierarchies through naming, tight (and loose) coupling is not so evident.
On mutating the SaltedHash: We can put mutate($new_algorithm, $password) into SaltedHash because the behavior is encapsulated in another switchable object (the SaltedHash itself is quite generic). Sort of like the State pattern.
On Chain of Responsibility: I've been wondering how to best represent the various authentication actions: logout, login, resume session, remember me. Since each of these operations is mutually exclusive, as whether or not the other one executes is dependent on each of the previous action's success (or failure), Chain of Responsibility would appear to be quite a good way of implementing it. We just set up the chain between a bunch of command objects like LogoutCommand, LoginCommand, etc, passing $credentials and $command_context down to be acted upon appropriately.
While this seems like an attractive way of solving the problem, there lies the question: are all authentication actions truly mutually exclusive? The only other version I can think of is perhaps TwoFactorLogin or TemporaryPasswordLogin: my imagination is not that fertile. One could argue that if a command is executed, it would be much happier operating as a pre/post-authenticate hook, but...
On a different note, we could use the Composite pattern to further abstract the chain. Instead of each of these discrete operations, we instead have EndSessionCommand and StartSessionCommand, and then Login and friends get categorized accordingl. Perhaps this obviates the need for a Chain of Responsibility on the higher level.
An observation: as one precedes further down the chain, the authentication method is of decreasing security/sureness. A login is more conclusive about identity then a session command. And the same applies for Session versus Remember me.
Another important question: is it really desirable to allow a user to login/logout on every single page, or should the request be directed to a special login page? If the latter is implemented, then the user's application would have to find some good facility for redirecting the user back to their original area, which is quite difficult. But indeed it is much conceptually cleaner.
Finally, the ResumeSessionCommand may not belong in such a command chain. Because we are making our authentication system Session-oriented, this would be important enough to be executed every time. We end up resuming the session every time: logging in creates the session right before it is resumed.
Conclusion - Reading Design Patterns by GoF gives some very interesting concepts to apply in this authentication system to maximize extensibility. If you need explanations for any of the patterns (italicized for your convenience) please ask. If Devnetwork is blocked by China's firewall, this might be my last post for two weeks.
Update: I hate when I'm constantly wrong on an issue. This should be the last update and I'll try to get the parts where I'm talking out my ass out of the post.
createIterator is the same as getIterator.
SPL Iterator also has
SPL rewind() or GOF First()
current() or currentItem()
key() : which you don't have to implement but still need to have the method.
valid() or isDone()
next() : same as in the GOF Iterator.
All three methods are using the same SPL iterator, which would you choose? Not based on speed comparsions.
#1
#2
#3
Number 1 and 3 are similar, but the former requires more work and calls to the method. Most would rather use either 2 or 3 because of old habits from PHP 4 and/or from another language iteration.
The SPL IteratorAggregate is the same as the GOF Iterator pattern. It even says the word aggregate in the paragraphs.
The Iterator is the Iterator and the IteratorAggregate is the AbstractList.
PHP SPL Iterator is far easier to understand and implement. You would have to have at least a year of C++ programming before you could implement the C++ versions. It is more along the lines of being able to use any class and call object->next. However since there is no built in parts (unless the class uses the STL Iterators and after trying to implement that, I believe my head exploded).
If you use Reflection, you could use the method isIterator() to see if the class implements SPL Iterator or rather the internal interface that Iterator implements.
Iterators in PHP aren't the best, but it gives something. Later in the language may offer better abstraction and features for Iterators.
SPL Iterators just allows you to interface with the Foreach that allow you to iterate over Arrays nicely for your classes. You would have to document what the current and key is (if the key is anything), but that would be about it. You would have to document what current returns anyway.
I don't know why I'm so worked up about something that wasn't even implemented by myself. However, I suppose the issue is more of that I use Iterators whenever I can and I think everyone else should. It is a generally good thing.
SPL Iterator implements everything of the GOF Iterator Design Pattern.Ambush Commander wrote:On Iterators: While reading the Gang of Four's Design Patterns, I finally got around to finding what exactly all this fuss about iterators is about. But I also realized something else:
In general, the iterator object is seperate from the data object, and you get an instance of the iterator using createIterator(). This is contrary to the SPL notion, where an object implements the Iterator interface and handles both data storage and iteration. Thus, while Gang of Four suggests two seperate objects, SPL meshes them together.
Also, it turns out that next() and reset()/first() form an even more simplified version of the iterator pattern, but they ARE iterators, nonetheless. This form maps most neatly to the MySQL functions. Alternatively:
Code: Select all
for($i->first(); !$i->isDone(); $i->next()) { // operate on $i->currentItem(); }
createIterator is the same as getIterator.
SPL Iterator also has
SPL rewind() or GOF First()
current() or currentItem()
key() : which you don't have to implement but still need to have the method.
valid() or isDone()
next() : same as in the GOF Iterator.
All three methods are using the same SPL iterator, which would you choose? Not based on speed comparsions.
#1
Code: Select all
for($i->rewind(); !$i->valid(); $i->next())
{
echo $i->current();
}Code: Select all
$i->rewind();
while(!$i->valid())
{
echo $i->current();
$i->next();
}Code: Select all
foreach($i as $current)
{
echo $current;
}The SPL IteratorAggregate is the same as the GOF Iterator pattern. It even says the word aggregate in the paragraphs.
The Iterator is the Iterator and the IteratorAggregate is the AbstractList.
PHP SPL Iterator is far easier to understand and implement. You would have to have at least a year of C++ programming before you could implement the C++ versions. It is more along the lines of being able to use any class and call object->next. However since there is no built in parts (unless the class uses the STL Iterators and after trying to implement that, I believe my head exploded).
If you use Reflection, you could use the method isIterator() to see if the class implements SPL Iterator or rather the internal interface that Iterator implements.
Iterators in PHP aren't the best, but it gives something. Later in the language may offer better abstraction and features for Iterators.
SPL Iterators just allows you to interface with the Foreach that allow you to iterate over Arrays nicely for your classes. You would have to document what the current and key is (if the key is anything), but that would be about it. You would have to document what current returns anyway.
If you are talking about databases, then it would not be a good idea. I know because PHP documentation says so and it is God, so I believe what God says. It would be on a small set of rows, but I would keep it simple and just fetch the data when it is needed and not everything.There's also the question of what the most optimized practice is. I'm starting to wonder whether or not it really matters if we load the entire result set into memory, rather, the number of function calls and the number of passes over the data are the true performance influence? Hmm...
Well, I agree with everything else except the Iterator. An Authentication/Authorization framework(?) or class package doesn't need Iterators, unless we want to handle the displaying of members.Conclusion - Reading Design Patterns by GoF gives some very interesting concepts to apply in this authentication system to maximize extensibility. If you need explanations for any of the patterns (italicized for your convenience) please ask. If Devnetwork is blocked by China's firewall, this might be my last post for two weeks.
I don't know why I'm so worked up about something that wasn't even implemented by myself. However, I suppose the issue is more of that I use Iterators whenever I can and I think everyone else should. It is a generally good thing.
I believe some packages require that you input the password after a period of time, even if you have remember me. However, I have only seen it in SMF and only in the admin panel where security is of highest priority over the general forum view.An observation: as one precedes further down the chain, the authentication method is of decreasing security/sureness. A login is more conclusive about identity then a session command. And the same applies for Session versus Remember me.
- Ambush Commander
- DevNet Master
- Posts: 3698
- Joined: Mon Oct 25, 2004 9:29 pm
- Location: New Jersey, US
Ah, IteratorAggregate. You're correct. Actually, if we use IteratorAggregate, we can make the thing backwards compatible quite easily with foreach() (just pass an array instead of an object implementing Iterator).
In the end, however, DB access is a subject that has been done to death. I don't think we should use cutting-edge iterator technology... yet. I think I will find use for it later though.
Okay guys, here's the next set of notes. Don't mind the bad formatting.
== Composition to Solve Under-the-Hood Unit-Testing Woes ==
We occasionally have trouble unit testing "under-the-hood" due to encapsulation.
While this is all fine and dandy preventing other objects from messing around,
it hinders Test Driven Development. In the DB class, a protected function
bindVariables() is shared through inheritance, and requires the definition of
another class in order to unit test it. By sharing this code through composition,
we obviate this need.
Inheritance: DB uses template methods so that it can do most of the heavy work
in the non-abstract public function execute() and the protected utility function
bindVar(). The template methods are escape() and realQuery(). Subclasses only
define these functions.
Composition: Besides the DB object, we also have a DBBinder, a DBEscaper and
a DBQuery. Depending on the database, DBEscaper and DBQuery vary, but a common
DBBinder is shared. DBBinder::bindVar() is public, so it can easily be tested,
and all the classes are a bit smaller (though there are more of them). You
would configure this for MySQL like:
This can all be done automatically via a factory method.
== Dealing with Legacy Hashing and Insufficient Columns ==
When we retrieve info from the database to instantiate a SaltedHash, the
information on the algorithms may not be present. This is because most existing
`user` tables do not anticipate the need to upgrade their hashing scheme.
No column means that the SQL must be modified accordingly. This requires
added indirection at the DBGateway/DBFacade level. Trickery may be possible
to reuse the column renaming system to dud-ify the column.
Without the type, several things need to happen:
* We need to know WHAT the hash is (auto-detection and configuration)
* We MUST NOT mutate the hash
* Gateway must transparently supply and discard hash-type info
Efficient configuration is tricky: the more flexible, the more complex.
See further discussion in the configuration section (below).
== Crucial Configuration ==
=== DB ===
DB credentials (type, user, pass, db, host): auto-detectable if client has
already opened a DB connection, but this method is not recommended.
Manual configuration necessary and expected.
[creation of DB wrapper establishes indirection]
DB column RENAMING (simple): reasonable defaults possible if AuthTools
creates the DB tables, otherwise, user must reconfigure the wrapper (however
no hard-coding is needed).
[DB wrapper establishes indirection]
DB column OMISSIONS: best example is SaltedHash. For expected omissions,
facilities will be provided, otherwise, this must be overcome through
a hard-coded compatibility layer.
[SQL generator establishes indirection]
DB column CROSS-TABLE: nearly impossible to do, so compatibility must be hard-coded.
Has performance implications. However, it's okay if multiple domain objects
coalesce into one table (it's only the opposite that's feared).
[SQL generator establishes indirection]
Table creation and maintenance: this could be troublesome. Obviously, if AuthTools
is plugging into existing tables, we really don't want to touch them. Tables
created by AuthTools from an installer script can and should be maintained.
However, what ever schema we make, oodles of backwards-compatibility will be
necessary whenever we change something. A manual detailing the finer aspects
of the schema may ease the updating pain. We would also need to know which
tables were AuthTools generated.
[Maintenance scripts establish indirection]
=== Flat-Files ===
Writing a file layer that can swap with the DB layer will be very troublesome.
Flat-file location: if AuthTools creates them, reasonable default, else needs to
be configured.
[FileGateway(?) establishes indirection]
File-format/Accessor routines: if AuthTools creates them, reasonable default, else
it needs to be hard-coded.
[FileGateway(?) establishes indirection]
=== View / Templates ===
Anyone using our templates will have to do a bit of customization to fit them in.
Web path: auto-detectable, hard-code preferred.
[?]
=== Security ===
Hash/salt strategy: toolset defines a reasonable default that reflects best-practices
and gets updated as new and better algorithms are found (mutation). Without a
hash-type column, the default must not reflect best-practices, but rather, reflect
the user's current algorithm.
[SaltedHash::factory() establishes indirection]
Remember me: Several issues here:
Same as user or seperate: If remember me is stored as a single column in the user
table, only one remember me will be valid. Preferred behavior is a dedicated
table for this, but you can get by.
[SQL generator establishes indirection]
Whether to allow: For high-security systems, remember me is discouraged. Several
users suggested that if remember me was built like a plugin, it could easily be
disconnected from the system. However, the changes needed to make it work
are widespread: from templates to the LoginCommand, lots of things need to
change. Careful attention to configuration of remember me necessary, as well
as what should determine whether or not it is on.
[Plugin(?)]
=== Controller ===
Mapping superglobals to specific arrays: default will assume that the relevant fields
are correctly named since the user is using our forms. It will do rudimentary
MQ cleaning (which means that it won't fix problematic escaped array keys). User
may remap by reassigning names or manually pass the values (somewhat troublesome,
since they will have to check each variable's existence.)
[Context/Command_Context establishes indirection]
Write access to cookies: reasonable default name, really no reason to overload. Also
needs MQ de-escaping (at least for read)? Dealing with non-web environments may
be silly. Lump everything in one cookie? In interest of keeping things small,
Remember Me may be the only non-session cookie needed, this wrapper is
unnecessary without remember me.
Write access to session: fairly essential for the well-being of the system. Disable
GET session passing. Session variable name can have fairly reasonable default.
Starting sessions may be deferred to application (auto-detectable) or done by
AuthTools, although, ideally, AuthTools is the only session manager. Custom session
handling would require hard-coding. Sessions should be only written to once?
Remember, we're dealing with a generalized notion, which is based on the idea
that a long, random, temporary string authenticates a user. This notion is scene
throughout cryptographic applications.
=== Propagation ===
If we pass back an authentication_status object, it would be deferred quite nicely.
But needs to be hard-coded.
== View Components ==
The most basic ones:
* Rich login form - form on page dedicated to logging in
* Condensed login form - placed on all pages (if desired)
* Client-side assymmetrical encryption JavaScript
* Registration screen - make it easy to build form, complex conroller action
* Account preferences, specifically changing password
* Status screens - login success, logged out, registration success
I18N could be troublesome.
== Easy Configuration ==
Configuring all these components, esp. view, would take a long time. Tutorial
would be nice, interactive installer script even nicer.
== Forms ==
Should we use tables?
=== Login ===
Username: _________
Password: _________
Remember me: [ ]
(This form is secure, learn more) *JS*
[ Login ] [ Reset(?) ]
=== Register ===
Bare minimum requirements for authentication,
Username: _________ [ check availability *JS/AJAX* ]
Password: _________
Repeat: _________
(Password strength: ###______) *JS*
Email: _________
Repeat: _________
| other app-specific items
[ Register ] [ Reset ]
== Temporary Password Management ==
=== Management screen ===
Description...
...
...
Temporary Passwords Left: ##
[ Logs ] [ More info ]
[ INVALIDATE ALL ]
Generate new:
Password: _________
Number to generate: __
Expiration date(?): _________
Panic password(?): _________
=== Generated screen ===
[no cache]
New passwords generated. Please print and store this page in a safe place.
a98whraedka83
rfaodhr2ehjkr
rfd909393923r
kg9239r3rkkfk
kfedjfe3r3433
...
=== Schema ===
== Throttling ==
1. First ## tries: Normal login screen
2. Failure: captcha login for this IP and for username
This needs login logging:
Fancy queries to check.
How to notify user of suspicious behavior?
== TuringTest ==
Generalize to TuringTest, which could be Catcha, Fig, etc.
Interface is kind of bad.
Turing tests break caching, so try to get them on dedicated pages.
=== Captcha ===
Must handle random word generation (localizable) and does not necessarily have
to employ PHP catpcha: MediaWiki uses a python script (with much success).
NO SESSIONS! Captchas uniquely identified.
== High Level Overlook ==
Userland configures AuthTools, then passes control to the main Authenticator.
Authenticator first handles Authentication Commands.
Then, it handles sessions.
Then it returns the authentication status.
With the status, Authorizer handles Authorization.
== Login Complexity ==
Login is a surprisingly complex action. Several questions:
What type?
* TwoFactor
* Temp
* Normal
* ?
What side effects?
* Remember me
* ?
This could concievably be part of the Chain of Responsibility, esp. StartSessionCommand
Normal + Temp are in same space and must be tested individually.
See more discussion about the internals.
== Authentication Status ==
Does the authentication status object know about the ChainOfResponsibility?
fromSession() - extremely sensitive ops require password to be sent on the same request
as the one that will invoke the action OR during the entry (although that's more
establishing a second, short-lived session (MULTIPLE SESSIONS???).
howAuthenticated() - could return the command object that authenticated it
isAuthenticated() - low level bool return
lastRequest() - time, handles decay and expire priviledges
How to establish a priviledge hierarchy? Is it a hierarchy to begin with?
The CoR sort of establishes it... if so:
hasNecessaryPriv($command) - tells us whether or the current howAuthenticated is good
enough for whatever is asking it.
Somewhat moving into authorization territory.
== Discarding Chain of Responsibility ==
TwoFactor authentication can roughly be decomposed into two schemes:
* regular PASSWORD auth
* twofactor's GEN_PASSWORd auth
A theoretical sci-fi security check might have these schemes:
* regular password check
* iris scan
* fingerprint check
* biometric check
(the important thing to note is that all of these things happen. They're cumulative).
An AuthenticationCommand, then, is defined as a bundle of authenticatable objects
(password, gen_password, iris_image ...) plus associated side effects (remember me,
start session! (after all, just authenticating someone doesn't start a session)).
Since we don't usually let a person mix-and-match authentication aspects ("I'll take
the iris check but not the fingerprint check") our Commands are the only way to get
authenticated. However, if we compose a command with processes, this CAN be possible,
and internally speaking, that's how the system treats it. More likely, however, it is
the system administrator that creates their own command by composing objects.
On the view side, we no longer have a LoginForm or a TwoFactorForm, but rather, we
can dynamically instantiate these forms by simply accepting the list of processes and
side effects and then assembling the proper input fields accordingly.
This new paradigm mortaly wounds the Chain of Responsibility notion. Let's look at our
original four commands again. Login, logout, remember me, and session. These are all
mutually exclusive... but that's only because none of their initial conditions overlap!
Another thought: if a user tries to simultaneously login and logout, should the
system...
* Log them in?
* Log them out? (the old answer)
* Reject the request as invalid and do nothing? (the new answer)
But, with flags, this means that we won't need complicated lookup tables to figure
the whole thing out:
Each command has a specific flag associated with it (corr. to their name, I presume).
This makes a controller's life VERY EASY. We can set up precedence for flags, and if
multiple flags have the same precedence, we toss out the whole thing. Actually, it's
almost like CoR on steroids, but it's a bit easier to dynamically add objects to the
chain even when you don't know muc about it. Static aliases for integer flags are a must.
If you really think about it, CoR was meant to deal with pre-existing objects. When we
are instantiating objects for the sole purpose of this, there really is no point.
For the rare command that doesn't have a cleanly defined flag, I suppose we'd need
a proxy. The session question is easily fixed by putting it elsewhere. Inherently
background processes need not be treated as commands.
Remember me, we're talking about you. While POSTed commands need direct user
intervention, cookie/session/http based commands do not. They happen automatically.
Thus, they'll always be there, even when they're not strictly needed.
But what determines need? Sessions. Thus, we check the session FIRST.
1. Attempt to resume session
if session resumed:
2. Execute Modify and Destroy commands
* Log out
* Elevate priviledges(?)
if there's no valid session:
2. Execute Create commands
* Log in
* Remember me
3. Create/destroy session accordingly
In the end, however, DB access is a subject that has been done to death. I don't think we should use cutting-edge iterator technology... yet. I think I will find use for it later though.
I'm still not convinced. We're going to fetch all the data eventually, esp. if it's just one row, so why not do it all at once?It would be on a small set of rows, but I would keep it simple and just fetch the data when it is needed and not everything.
Actually, I've been thinking about that myself too.I believe some packages require that you input the password after a period of time, even if you have remember me. However, I have only seen it in SMF and only in the admin panel where security is of highest priority over the general forum view.
Okay guys, here's the next set of notes. Don't mind the bad formatting.
== Composition to Solve Under-the-Hood Unit-Testing Woes ==
We occasionally have trouble unit testing "under-the-hood" due to encapsulation.
While this is all fine and dandy preventing other objects from messing around,
it hinders Test Driven Development. In the DB class, a protected function
bindVariables() is shared through inheritance, and requires the definition of
another class in order to unit test it. By sharing this code through composition,
we obviate this need.
Inheritance: DB uses template methods so that it can do most of the heavy work
in the non-abstract public function execute() and the protected utility function
bindVar(). The template methods are escape() and realQuery(). Subclasses only
define these functions.
Composition: Besides the DB object, we also have a DBBinder, a DBEscaper and
a DBQuery. Depending on the database, DBEscaper and DBQuery vary, but a common
DBBinder is shared. DBBinder::bindVar() is public, so it can easily be tested,
and all the classes are a bit smaller (though there are more of them). You
would configure this for MySQL like:
Code: Select all
$escaper = new DBEscaper_MySQL($handle);
$binder = new DBBinder($escaper);
$query = new DBQuery($handle);== Dealing with Legacy Hashing and Insufficient Columns ==
When we retrieve info from the database to instantiate a SaltedHash, the
information on the algorithms may not be present. This is because most existing
`user` tables do not anticipate the need to upgrade their hashing scheme.
No column means that the SQL must be modified accordingly. This requires
added indirection at the DBGateway/DBFacade level. Trickery may be possible
to reuse the column renaming system to dud-ify the column.
Without the type, several things need to happen:
* We need to know WHAT the hash is (auto-detection and configuration)
* We MUST NOT mutate the hash
* Gateway must transparently supply and discard hash-type info
Efficient configuration is tricky: the more flexible, the more complex.
See further discussion in the configuration section (below).
== Crucial Configuration ==
=== DB ===
DB credentials (type, user, pass, db, host): auto-detectable if client has
already opened a DB connection, but this method is not recommended.
Manual configuration necessary and expected.
[creation of DB wrapper establishes indirection]
DB column RENAMING (simple): reasonable defaults possible if AuthTools
creates the DB tables, otherwise, user must reconfigure the wrapper (however
no hard-coding is needed).
[DB wrapper establishes indirection]
DB column OMISSIONS: best example is SaltedHash. For expected omissions,
facilities will be provided, otherwise, this must be overcome through
a hard-coded compatibility layer.
[SQL generator establishes indirection]
DB column CROSS-TABLE: nearly impossible to do, so compatibility must be hard-coded.
Has performance implications. However, it's okay if multiple domain objects
coalesce into one table (it's only the opposite that's feared).
[SQL generator establishes indirection]
Table creation and maintenance: this could be troublesome. Obviously, if AuthTools
is plugging into existing tables, we really don't want to touch them. Tables
created by AuthTools from an installer script can and should be maintained.
However, what ever schema we make, oodles of backwards-compatibility will be
necessary whenever we change something. A manual detailing the finer aspects
of the schema may ease the updating pain. We would also need to know which
tables were AuthTools generated.
[Maintenance scripts establish indirection]
=== Flat-Files ===
Writing a file layer that can swap with the DB layer will be very troublesome.
Flat-file location: if AuthTools creates them, reasonable default, else needs to
be configured.
[FileGateway(?) establishes indirection]
File-format/Accessor routines: if AuthTools creates them, reasonable default, else
it needs to be hard-coded.
[FileGateway(?) establishes indirection]
=== View / Templates ===
Anyone using our templates will have to do a bit of customization to fit them in.
Web path: auto-detectable, hard-code preferred.
[?]
=== Security ===
Hash/salt strategy: toolset defines a reasonable default that reflects best-practices
and gets updated as new and better algorithms are found (mutation). Without a
hash-type column, the default must not reflect best-practices, but rather, reflect
the user's current algorithm.
[SaltedHash::factory() establishes indirection]
Remember me: Several issues here:
Same as user or seperate: If remember me is stored as a single column in the user
table, only one remember me will be valid. Preferred behavior is a dedicated
table for this, but you can get by.
[SQL generator establishes indirection]
Whether to allow: For high-security systems, remember me is discouraged. Several
users suggested that if remember me was built like a plugin, it could easily be
disconnected from the system. However, the changes needed to make it work
are widespread: from templates to the LoginCommand, lots of things need to
change. Careful attention to configuration of remember me necessary, as well
as what should determine whether or not it is on.
[Plugin(?)]
=== Controller ===
Mapping superglobals to specific arrays: default will assume that the relevant fields
are correctly named since the user is using our forms. It will do rudimentary
MQ cleaning (which means that it won't fix problematic escaped array keys). User
may remap by reassigning names or manually pass the values (somewhat troublesome,
since they will have to check each variable's existence.)
[Context/Command_Context establishes indirection]
Write access to cookies: reasonable default name, really no reason to overload. Also
needs MQ de-escaping (at least for read)? Dealing with non-web environments may
be silly. Lump everything in one cookie? In interest of keeping things small,
Remember Me may be the only non-session cookie needed, this wrapper is
unnecessary without remember me.
Write access to session: fairly essential for the well-being of the system. Disable
GET session passing. Session variable name can have fairly reasonable default.
Starting sessions may be deferred to application (auto-detectable) or done by
AuthTools, although, ideally, AuthTools is the only session manager. Custom session
handling would require hard-coding. Sessions should be only written to once?
Remember, we're dealing with a generalized notion, which is based on the idea
that a long, random, temporary string authenticates a user. This notion is scene
throughout cryptographic applications.
=== Propagation ===
If we pass back an authentication_status object, it would be deferred quite nicely.
But needs to be hard-coded.
== View Components ==
The most basic ones:
* Rich login form - form on page dedicated to logging in
* Condensed login form - placed on all pages (if desired)
* Client-side assymmetrical encryption JavaScript
* Registration screen - make it easy to build form, complex conroller action
* Account preferences, specifically changing password
* Status screens - login success, logged out, registration success
I18N could be troublesome.
== Easy Configuration ==
Configuring all these components, esp. view, would take a long time. Tutorial
would be nice, interactive installer script even nicer.
== Forms ==
Should we use tables?
=== Login ===
Username: _________
Password: _________
Remember me: [ ]
(This form is secure, learn more) *JS*
[ Login ] [ Reset(?) ]
=== Register ===
Bare minimum requirements for authentication,
Username: _________ [ check availability *JS/AJAX* ]
Password: _________
Repeat: _________
(Password strength: ###______) *JS*
Email: _________
Repeat: _________
| other app-specific items
[ Register ] [ Reset ]
== Temporary Password Management ==
=== Management screen ===
Description...
...
...
Temporary Passwords Left: ##
[ Logs ] [ More info ]
[ INVALIDATE ALL ]
Generate new:
Password: _________
Number to generate: __
Expiration date(?): _________
Panic password(?): _________
=== Generated screen ===
[no cache]
New passwords generated. Please print and store this page in a safe place.
a98whraedka83
rfaodhr2ehjkr
rfd909393923r
kg9239r3rkkfk
kfedjfe3r3433
...
=== Schema ===
Code: Select all
TABLE `temp_password` COLUMNS (
id INT PRIMARY_KEY,
user_id INT,
salted_hash STRING,
salt STRING,
salted_hash_algorithm STRING,
expiration INT,
valid BOOL,
used_on INT
);1. First ## tries: Normal login screen
2. Failure: captcha login for this IP and for username
This needs login logging:
Code: Select all
TABLE `login_log` COLUMNS (
id INT PRIMARY_KEY,
user_id INT,
time INT,
success BOOL,
with_captcha BOOL,
ip_address STRING,
type STRING ( normal, temp, etc. ),
attempt STRING ( if not successful, store the attempted password )
);How to notify user of suspicious behavior?
== TuringTest ==
Generalize to TuringTest, which could be Catcha, Fig, etc.
Code: Select all
interface TuringTest
{
protected $answer;
public function validate($answer);
public function paintQuestion(); // returns test, like image or audio stream
}Turing tests break caching, so try to get them on dedicated pages.
=== Captcha ===
Must handle random word generation (localizable) and does not necessarily have
to employ PHP catpcha: MediaWiki uses a python script (with much success).
NO SESSIONS! Captchas uniquely identified.
== High Level Overlook ==
Userland configures AuthTools, then passes control to the main Authenticator.
Authenticator first handles Authentication Commands.
Then, it handles sessions.
Then it returns the authentication status.
With the status, Authorizer handles Authorization.
== Login Complexity ==
Login is a surprisingly complex action. Several questions:
What type?
* TwoFactor
* Temp
* Normal
* ?
What side effects?
* Remember me
* ?
This could concievably be part of the Chain of Responsibility, esp. StartSessionCommand
Normal + Temp are in same space and must be tested individually.
See more discussion about the internals.
== Authentication Status ==
Does the authentication status object know about the ChainOfResponsibility?
fromSession() - extremely sensitive ops require password to be sent on the same request
as the one that will invoke the action OR during the entry (although that's more
establishing a second, short-lived session (MULTIPLE SESSIONS???).
howAuthenticated() - could return the command object that authenticated it
isAuthenticated() - low level bool return
lastRequest() - time, handles decay and expire priviledges
How to establish a priviledge hierarchy? Is it a hierarchy to begin with?
The CoR sort of establishes it... if so:
hasNecessaryPriv($command) - tells us whether or the current howAuthenticated is good
enough for whatever is asking it.
Somewhat moving into authorization territory.
== Discarding Chain of Responsibility ==
TwoFactor authentication can roughly be decomposed into two schemes:
* regular PASSWORD auth
* twofactor's GEN_PASSWORd auth
A theoretical sci-fi security check might have these schemes:
* regular password check
* iris scan
* fingerprint check
* biometric check
(the important thing to note is that all of these things happen. They're cumulative).
An AuthenticationCommand, then, is defined as a bundle of authenticatable objects
(password, gen_password, iris_image ...) plus associated side effects (remember me,
start session! (after all, just authenticating someone doesn't start a session)).
Since we don't usually let a person mix-and-match authentication aspects ("I'll take
the iris check but not the fingerprint check") our Commands are the only way to get
authenticated. However, if we compose a command with processes, this CAN be possible,
and internally speaking, that's how the system treats it. More likely, however, it is
the system administrator that creates their own command by composing objects.
On the view side, we no longer have a LoginForm or a TwoFactorForm, but rather, we
can dynamically instantiate these forms by simply accepting the list of processes and
side effects and then assembling the proper input fields accordingly.
This new paradigm mortaly wounds the Chain of Responsibility notion. Let's look at our
original four commands again. Login, logout, remember me, and session. These are all
mutually exclusive... but that's only because none of their initial conditions overlap!
Code: Select all
Flags | Login | Logout | Session | Remember |
---------+-------+--------+---------+----------+
Login | req | | | ign |
Logout | | req | req | ign | <-- one can't logout without having
Session | | | req | ign | been logged in!
Remember | | | | req |
^
notice how the remember me flag can be on, but
normally has no bearing on the other operations.system...
* Log them in?
* Log them out? (the old answer)
* Reject the request as invalid and do nothing? (the new answer)
But, with flags, this means that we won't need complicated lookup tables to figure
the whole thing out:
Each command has a specific flag associated with it (corr. to their name, I presume).
This makes a controller's life VERY EASY. We can set up precedence for flags, and if
multiple flags have the same precedence, we toss out the whole thing. Actually, it's
almost like CoR on steroids, but it's a bit easier to dynamically add objects to the
chain even when you don't know muc about it. Static aliases for integer flags are a must.
If you really think about it, CoR was meant to deal with pre-existing objects. When we
are instantiating objects for the sole purpose of this, there really is no point.
For the rare command that doesn't have a cleanly defined flag, I suppose we'd need
a proxy. The session question is easily fixed by putting it elsewhere. Inherently
background processes need not be treated as commands.
Remember me, we're talking about you. While POSTed commands need direct user
intervention, cookie/session/http based commands do not. They happen automatically.
Thus, they'll always be there, even when they're not strictly needed.
But what determines need? Sessions. Thus, we check the session FIRST.
1. Attempt to resume session
if session resumed:
2. Execute Modify and Destroy commands
* Log out
* Elevate priviledges(?)
if there's no valid session:
2. Execute Create commands
* Log in
* Remember me
3. Create/destroy session accordingly
That is a lot to read and I haven't read through it all, but I will. Um, I suppose, we'll probably move on from the Iterator stuff until it maybe comes up again. Most likely won't.
However, one more thing. I believe if you fetch all of the data first, MySQL or the database sends it all back to PHP, which can be a huge chunk. That chunk has to be processed and go through the pipeline to the server PHP is on (if they are separate servers). I'm probably talking out my ass, but other methods PHP calls for the first row and MySQL sends just that row and then moves the counter up to send the next row that meets the criteria.
So it breaks down to sending all of the data at once or just a tiny bit at a time. You will eventually fetch all of the data, but if you so happen that you don't need to go any further after say row 1000 out of a million, then you saved yourself a lot of data that would have otherwise had been sent.
That is at least from my understanding from reading the PHP manual. Most likely wrong, but simplified version at least.
Forms
Tables would be nice and standard. They may not like having a table within a table, but since you are separating the template out anyway, they can just edit it themselves.
No One uses Reset Anymore
How about a Register button instead that transfers to another page (using JavaScript?) that will allow them to register.
However, one more thing. I believe if you fetch all of the data first, MySQL or the database sends it all back to PHP, which can be a huge chunk. That chunk has to be processed and go through the pipeline to the server PHP is on (if they are separate servers). I'm probably talking out my ass, but other methods PHP calls for the first row and MySQL sends just that row and then moves the counter up to send the next row that meets the criteria.
So it breaks down to sending all of the data at once or just a tiny bit at a time. You will eventually fetch all of the data, but if you so happen that you don't need to go any further after say row 1000 out of a million, then you saved yourself a lot of data that would have otherwise had been sent.
That is at least from my understanding from reading the PHP manual. Most likely wrong, but simplified version at least.
Forms
Tables would be nice and standard. They may not like having a table within a table, but since you are separating the template out anyway, they can just edit it themselves.
No One uses Reset Anymore
How about a Register button instead that transfers to another page (using JavaScript?) that will allow them to register.
