Let's start with specific case of views, which in sqlStandard mode
will always run with the definer authorization.
eg
say view v1 is defined by user "u1", and it does a select from a view
v2 defined by user "u2". And view v2 does a select from table t2,
which is also owned by user "u2".
Now, when user "u1", after connecting to the database, runs select *
from v1; the authorizers involved at different times during the
execution of this sql will be different. First authorizer involved will
always be for "u1", since "u1" is the user who has made connection to
the database. This first authorizer is also the invoker authorizer. At
the start of execution of select * from v1 by user "u1", since v1 is
owned by "u1", the authorizer for "u1" should be used at that point.
Next during the
execution of select * from v1, Derby is going to access v2 which is
defined by user "u2" and hence, while accessing v2, the authorizer used
should be corresponding to definer"u2". When the accessing of v2 is
over, Derby should discard the authorizer for view definer "u2" and go
back to authorizer for invoker "u1" to finish exeucting the rest of
select * from v1. And at last, when execution of select * from v1 is
over, the only user authorizer active should be the one for user "u1",
who had made connection to the database for this session.
I am considering implementing above scenario of switching from one
authorizer to another by keeping a stack of authorizers in
GenericLanguageConnectionContext.
I don't think this model could work for views... Derby resolves views
during binding by merging base view definition into the query that
references the view. (For example:
In addition, two methods, addAuthorizerToTopOfStack and
removeAuthorizerFromTopOfStack will be added to
GenericLanguageConnectionContext and calls to these methods will be
generated during the generation phase of a sql depending on what that
sql's needs are for different authorizers. It will be the
responsibility of the sql generation phase to generate calls to
removeAuthorizerFromTopOfStack when it is finished with the authorizer
that it added earlier using addAuthorizerToTopOfStack. The code changes
for this new definer/invoker mode will also involve changing the
signature of GenericLanguageConnectionContext's initialize method, such
that the required authorizer name will be passed to this method and
initialize method will create a GenericAuthorizer object using that
name and will put that Authorizer on top of the authorizer stack. In
addition, initialize method will also create default schema descriptor
for that authorizer name and will put that on schema descriptor on top
of the default schema descriptor stack. The
GenericLanguageConnectionContext's changed initialize method will look
something like this.
public void initialize(String authorizerName, boolean sqlConnection)
throws StandardException
{
//
//Creating the authorizer authorizes the connection.
Authorizer authorizer = new
GenericAuthorizer(getAuthorizationId(authorizerName),this,
sqlConnection);
stackOfAuthorizers.addElement(authorizer);
//we can ignore the following if this is a database connection
//associated with internal thread such as logSniffer and StageTrunc
if(!sqlConnection)
return;
/*
** Set the authorization id. User shouldn't
** be null or else we are going to blow up trying
** to create a schema for this user.
*/
if (SanityManager.DEBUG)
{
if (getAuthorizationId() == null)
{
SanityManager.THROWASSERT("User name is null," +
" check the connection manager to make sure it is set" +
" reasonably");
}
}
SchemaDescriptor sd = initDefaultSchemaDescriptor();
stackOfSessionsForAuthorizers.addElement(sd);
setDefaultSchema(sd);
}
A call to this initialize method should be generated during the
generation phase if an object requires a need to switch to the object
definer's authorization. The code generation will involve passing the
name of the object definer to GenericLanguageConnectionContext's
initialize method and authorizer for the definer will be put
on GenericLanguageConnectionContext's stack of authorizers. If an
object needs to run with the invoker's authorization, code should be
generated to peek at the first authorizer on the stack of authorizers
(the first authorizer will always correspond to invoker's authorizer)
and that authorizer will need to be added to the top of authorizer
stack by generating code addAuthorizerToTopOfStack. To peek at the
first element of stack to get invoker's authorizer, I am planning on
adding method
getAuthorizerForConnectedUser to GenericLanguageConnectionContext.
Irrespective of which authorizer was added (definer/invoker), the
sql code generation phase should make sure that it generates the code
to pop that authorizer from the stack when it is finished with that
authorizer.
Example of code generation for adding an invoker authorizer
//We want to put the INVOKER as the authorizer on
LanguageConnectionContext's authorizer stack.
//We can find the INVOKER authorizer by looking at first element of
LanguageConnectionContext's
//authorizer stack which will always be the INVOKER of the sql
statement. Hence, we simply
//generate the code to copy the first element of the
LanguageConnectionContext's authorizer stack
//to the top of the stack.
acb.pushThisAsActivation(mb);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);
acb.pushThisAsActivation(mb);
mb.callMethod
(VMOpcode.INVOKEINTERFACE, null,
"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
"getAuthorizerForConnectedUser", "
org.apache.derby.iapi.sql.conn.Authorizer", 0);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
"addAuthorizerToTopOfStack", "void", 1);
Example of code generation for adding a definer authorizer
//We want to put the DEFINER authorizer by finding the owner name of
the object and generating
//code to call GenericLanguageConnectionContext.initialize method on
that owner name.
//GenericLanguageConnectionContext.initialize method will generate the
authorizer for the passed
//name and will add that authorizer to top of stack of
GenericLanguageConnectionContext's authorizer stack.
acb.pushThisAsActivation(mb);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);
String objectOwnerName = getDataDictionary().getSchemaDescriptor(
objectSchemaUUID,
getLanguageConnectionContext().getTransactionCompile()).getSchemaName();
mb.push(objectOwnerName);
mb.push(true);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null, "initialize", "void",
2);
Example of code generation for popping the authorizer that was
added earlier and should not be used anymore
acb.pushThisAsActivation(mb);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);
mb.callMethod(VMOpcode.INVOKEINTERFACE, null,
"removeAuthorizerFromTopOfStack", "void", 0);
In case the sql being executed runs into an exception, as part
of the cleanup work, we need to remove all the authorizers (except the
first one on the stack since it belongs to the "invoker" ie the user
who has made this connection to the database) and their corresponding
default schema descriptors from GenericLanugaugeConnectionContext's
stack. The additional code in
GenericLanugaugeConnectionContext.cleanupOnError will be as follows
/*
** In case of error, need to cleanup the intermediate authorizers
** and their corresponding default schema descriptors.
** Remove all the authorizers and default schema descriptors except
** for the first one on the stack because first authorizer
corresponds
** to the user who has made this database connection
*/
for (int i = stackOfAuthorizers.size() - 1; i >= 1; i--) {
stackOfAuthorizers.removeElementAt(i);
stackOfSessionsForAuthorizers.removeElementAt
(i);
}
This logic is pretty simple and straight forward but can be
confusing at the same time. So, if anyonehas any questions, pass them
on my way.
thanks,
Mamta