db-derby-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Satheesh Bandaram <sathe...@Sourcery.Org>
Subject Re: [Grant/Revoke]Proposal for invoker/definer model
Date Tue, 14 Mar 2006 01:13:36 GMT
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
  <meta content="text/html;charset=ISO-8859-1" http-equiv="Content-Type">
  <title></title>
</head>
<body bgcolor="#ffffff" text="#000000">
<br>
<br>
Mamta Satoor wrote:
<blockquote
 cite="midd9619e4a0603131049l3f0d8154l6976eac2ea0be351@mail.gmail.com"
 type="cite">
  <p>While going through some of the Derbylist mails on Grant/Revoke, I
realized that various database objects in a sqlStandard mode&nbsp;may
require switching authorizers from the invoker to different definers
and vice versa. This piece of task sounds interesting to me and hence I
have following proposal for implementing it.</p>
</blockquote>
I think it would be good to list what database objects you are looking
to implement definer logic for. Derby currently uses invoker model for
all database objects, like triggers, views and this new execution model
in sqlAuthorization mode would be different. The Grant &amp; Revoke
spec also calls for droping some of these objects when privileges they
need are revoked later.<br>
<blockquote
 cite="midd9619e4a0603131049l3f0d8154l6976eac2ea0be351@mail.gmail.com"
 type="cite">
  <p></p>
  <p>Let's start with specific case of views, which in sqlStandard mode
will always run with the definer authorization. <br>
eg<br>
say view v1 is defined by user "u1", and it does a select from a view
v2 defined by user "u2".&nbsp;&nbsp;And view v2 does a select from table t2,
which is also owned by user "u2". <br>
Now, when user "u1", after connecting to the database, runs select *
from v1; the authorizers&nbsp;involved at different times during the
execution of this sql will be different. First authorizer involved&nbsp;will
always be for "u1", since "u1" is the user who has made connection to
the database. This first&nbsp;authorizer is also the invoker authorizer. At
the start of execution of select * from v1 by user "u1",&nbsp;since v1 is
owned by "u1", the authorizer for "u1" should be used at that point.
Next during the
  <br>
execution of select * from v1, Derby is going to access v2 which is
defined by user "u2" and hence,&nbsp;while accessing v2, the authorizer used
should be corresponding to definer"u2". When the accessing&nbsp;of v2 is
over, Derby should discard the authorizer for view definer "u2" and go
back to authorizer&nbsp;for invoker "u1" to finish exeucting the rest of
select * from v1. And at last, when execution of&nbsp;select * from v1 is
over, the only user authorizer active should be the one for user "u1",
who had&nbsp;made connection to the database for this session.
  </p>
  <p>I am considering implementing above scenario of switching from one
authorizer to another by keeping a stack of authorizers in
GenericLanguageConnectionContext.</p>
</blockquote>
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: <b>Select a from V1</b>, with V1
being defined as <b>SELECT a,b from t1</b> would become <b>select a
from (select a, b from t1)</b>) This further gets resolved during
preprocessing into something like <b>select a from t1</b>, by
unnesting the subquery. (where possible) So by the time Derby gets into
generation, there are no references to views, right?<br>
<blockquote
 cite="midd9619e4a0603131049l3f0d8154l6976eac2ea0be351@mail.gmail.com"
 type="cite">
  <p>Prior to intoduction of sqlStandard mode for Grant/Revoke, Derby
was written to run everything with invoker as the authorizer. This
single invoker authorizer information is currently kept in
GenericLanguageConnectionContext's authorizer(GenericAuthorizer) field.
This field gets set to invoker's authorizer when
GenericLanguageConnectionContext gets initialized at the database
connect time. This happens in
GenericLanguageConnectionContext.initilize method. In addition to
initilizing authorizer, this method also sets the default schema to a
schema with the same name as the invoker. So, as we can see,
GenericLanguageConnectionContext currently just deals with one
authorizer and&nbsp;that authorizer is always the authorizer corresponding
to the user who has made connection to the database.
  </p>
  <p>But with the addition of Grant/Revoke to Derby, we can have
different authorizers active during the execution of a single sql
statement. To support multiple authorizers,
GenericLanguageConnectionContext will need to keep a stack of these
authorizers and a corresponding stack of default schemas descriptors
for those authorizers. To achieve this, I am thinking of replacing
"authorizer" field in GenericLanguageConnectionContext with Vector
stackOfAuthorizers and Vector stackOfDefaultSchemasForAuthorizers. The
initialize methd of GenericLanguageConnectionContext at the database
connection time will create an authorizer for the user who made the
connection to the database and this authorizer will be added as the
first element to stackOfAuthorizers. This authorizer will stay on
GenericLanguageConnectionContext's stack of authorizers until the user
disconnects from the database. In addition, the default schema of this
authorizer will be pushed on to the stack
stackOfDefaultSchemasForAuthorizers.</p>
</blockquote>
This might work for triggers and constraints... other two database
objects that need to implement definer execution model. But I have to
think about it little more... to see if
GenericLanguageConnectionContext is the right place for this. Don't
triggers and constraints already have some soft of execution context
that gets pushed or poped? Does it make sense to plug this mechanism
into this? I have to research more on these questions..<br>
<br>
It seems to me we need two different solutions... one for views and
another for triggers/constraints. It is possible to extend what we may
do for triggers/constraints later to routines, but views need to be
handled differently. Much like synonyms, if we decided to extend
definer model to synonyms. I don't know yet if synonyms are supposed to
follow definer or invoker model. <br>
<br>
Satheesh<br>
<blockquote
 cite="midd9619e4a0603131049l3f0d8154l6976eac2ea0be351@mail.gmail.com"
 type="cite">
  <p> </p>
  <p>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.
  <br>
&nbsp;public void initialize(String authorizerName, boolean sqlConnection)
throws StandardException<br>
&nbsp;{<br>
&nbsp;&nbsp;//<br>
&nbsp;&nbsp;//Creating the authorizer authorizes the connection.<br>
&nbsp;&nbsp;Authorizer authorizer = new
GenericAuthorizer(getAuthorizationId(authorizerName),this,
sqlConnection);
  <br>
&nbsp;&nbsp;stackOfAuthorizers.addElement(authorizer);</p>
  <p>&nbsp;&nbsp;//we can ignore the following if this is a database connection<br>
&nbsp;&nbsp;//associated with internal thread such as logSniffer and StageTrunc<br>
&nbsp;&nbsp;if(!sqlConnection)<br>
&nbsp;&nbsp;&nbsp;return;</p>
  <p>&nbsp;&nbsp;/*<br>
&nbsp;&nbsp;** Set the authorization id.&nbsp; User shouldn't<br>
&nbsp;&nbsp;** be null or else we are going to blow up trying<br>
&nbsp;&nbsp;** to create a schema for this user.<br>
&nbsp;&nbsp;*/<br>
&nbsp;&nbsp;if (SanityManager.DEBUG)<br>
&nbsp;&nbsp;{<br>
&nbsp;&nbsp;&nbsp;if (getAuthorizationId() == null)
  <br>
&nbsp;&nbsp;&nbsp;{<br>
&nbsp;&nbsp;&nbsp;&nbsp;SanityManager.THROWASSERT("User name is null," +<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" check the connection manager to make sure
it is set" +<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" reasonably");<br>
&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;}</p>
  <p><br>
&nbsp;&nbsp;SchemaDescriptor sd = initDefaultSchemaDescriptor();<br>
&nbsp;&nbsp;stackOfSessionsForAuthorizers.addElement(sd);<br>
&nbsp;&nbsp;setDefaultSchema(sd);&nbsp;&nbsp;<br>
&nbsp;}</p>
  <p>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
  <br>
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 <br>
getAuthorizerForConnectedUser to GenericLanguageConnectionContext.</p>
  <p>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. </p>
  <div>Example of code generation for adding an invoker authorizer<br>
&nbsp;//We want to put the INVOKER as the authorizer on
LanguageConnectionContext's authorizer stack. <br>
&nbsp;//We can find the INVOKER authorizer by looking at first element of
LanguageConnectionContext's <br>
&nbsp;//authorizer stack which will always be the INVOKER of the sql
statement. Hence, we simply<br>
&nbsp;//generate the code to copy the first element of the
LanguageConnectionContext's authorizer stack<br>
&nbsp;//to the top of the stack.
  <br>
&nbsp;acb.pushThisAsActivation(mb);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);<br>
&nbsp;acb.pushThisAsActivation(mb);<br>
&nbsp;mb.callMethod
(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"getAuthorizerForConnectedUser", "
org.apache.derby.iapi.sql.conn.Authorizer", 0);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"addAuthorizerToTopOfStack", "void", 1);</div>
  <div><br>
Example of code generation for adding a definer authorizer<br>
&nbsp;//We want to put the DEFINER authorizer by finding the owner name of
the object and generating<br>
&nbsp;//code to call GenericLanguageConnectionContext.initialize method on
that owner name. <br>
&nbsp;//GenericLanguageConnectionContext.initialize method will generate the
authorizer for the passed<br>
&nbsp;//name and will add that authorizer to top of stack of
GenericLanguageConnectionContext's authorizer stack.
  <br>
&nbsp;acb.pushThisAsActivation(mb);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);<br>
&nbsp;String objectOwnerName = getDataDictionary().getSchemaDescriptor(
  <br>
&nbsp;&nbsp;objectSchemaUUID,
getLanguageConnectionContext().getTransactionCompile()).getSchemaName();<br>
&nbsp;mb.push(objectOwnerName);<br>
&nbsp;mb.push(true);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null, "initialize", "void",
2);
  </div>
  <p>Example of code generation for popping the authorizer that was
added earlier and should not be used anymore<br>
&nbsp;acb.pushThisAsActivation(mb);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"getLanguageConnectionContext", ClassName.LanguageConnectionContext,
0);<br>
&nbsp;mb.callMethod(VMOpcode.INVOKEINTERFACE, null,<br>
&nbsp;&nbsp;"removeAuthorizerFromTopOfStack", "void", 0);</p>
  <div>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</div>
  <div>&nbsp;&nbsp;/*<br>
&nbsp;&nbsp;** In case of error, need to cleanup the intermediate authorizers<br>
&nbsp;&nbsp;** and their corresponding default schema descriptors.<br>
&nbsp;&nbsp;** Remove all the authorizers and default schema descriptors except<br>
&nbsp;&nbsp;** for the first one on the stack because first authorizer
corresponds
  <br>
&nbsp;&nbsp;** to the user who has made this database connection<br>
&nbsp;&nbsp; */<br>
&nbsp;&nbsp;for (int i = stackOfAuthorizers.size() - 1; i &gt;= 1; i--) {<br>
&nbsp;&nbsp;&nbsp;stackOfAuthorizers.removeElementAt(i);<br>
&nbsp;&nbsp;&nbsp;stackOfSessionsForAuthorizers.removeElementAt
(i);<br>
&nbsp;&nbsp;}<br>
&nbsp;</div>
  <div>&nbsp;</div>
  <div>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.<br>
&nbsp;</div>
  <div>thanks,</div>
  <div>Mamta</div>
</blockquote>
</body>
</html>


Mime
View raw message