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.
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.
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?
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.
Actually, I've been thinking about that myself too.
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);
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 ===
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
);
== Throttling ==
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 )
);
Fancy queries to check.
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
}
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!
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.
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