2

I'm writing an online videogame Database in SQL using ORACLE for an accademic project, and i'm trying to create a trigger that for every user that submit their information in my ACCCOUNT TABLE

CREATE TABLE ACCOUNT (
USERNAME              VARCHAR(20),
PASSWORD              VARCHAR(20)            NOT NULL,
NATIONALITY           VARCHAR(15),
CREATION DATE         DATE,
EMAIL_ACCOUNT         VARCHAR(35)            NOT NULL,
CONSTRAINT            KEYACCOUNT          PRIMARY KEY(USERNAME),
CONSTRAINT            NO_USER_CSPEC          CHECK(REGEXP_LIKE(USERNAME, '^[a-zA-Z0-9._]+$') AND USERNAME NOT LIKE '% %'),
CONSTRAINT            NO_EASY_PASS           CHECK(REGEXP_LIKE(PASSWORD, '^[a-zA-Z0-9._!#£$%&/()=?]') AND PASSWORD NOT LIKE '% %'),
CONSTRAINT            LENGHTUSER          CHECK(LENGTH(USERNAME)>3),
CONSTRAINT            LENGHTPASS          CHECK(LENGTH(PASSWORD)>5),
CONSTRAINT            FK_EMAIL               FOREIGN KEY(EMAIL_ACCOUNT) REFERENCES PERSONA(EMAIL) ON DELETE CASCADE
);

Will fire a trigger that will create a new user with the new username and password just inserted.

this is the code i tried to wrote

 CREATE OR REPLACE TRIGGER NEW_USER
 AFTER INSERT ON ACCOUNT
 FOR EACH ROW
 BEGIN
 CREATE USER :NEW.USERNAME IDENTIFIED BY :NEW.PASSWORD;
 GRANT ROLE_NAME TO :NEW.USERNAME
 END;

Why i'm tyring to do this ? Basically because i'd like to give specific view on specific row that regards only the specific user. ( imagine if, while managing your account you can access to every other row stored in the table ACCOUNT )

After creating that specific user i can create some procedure that have in input the username ( of a successfully created user ) and give back the view on that specific row.

is there a way to do this ?

GanniH
  • 23
  • 1
  • 4
  • Your procedure can't just select the specific row from your table anyway, based on the passed-in user name? Why does having an Oracle user account make any difference to that procedure? (Maybe you're planning on having the user connect to the DB as themselves anyway for other reasons, but not sure this scenario on its own makes sense). – Alex Poole Jun 19 '17 at 10:22
  • 2
    You can't use DDL inside a trigger, because a DDL statement **commits** the current transaction and you can't do that in a trigger –  Jun 19 '17 at 10:23
  • well, whats the scenario so ? i'd like to create n user for n account. or at least, try to grant the access only to the row related to the specific user. – GanniH Jun 19 '17 at 10:34
  • @GanniH Why not use a database Stored Procedure to insert rows into `ACCOUNT` table and in the same procedure create a user with password after the user gets inserted successfully? – Jacob Jun 19 '17 at 10:35
  • thats sound a great idea. is this even possible ? – GanniH Jun 19 '17 at 10:48
  • @GanniH Yes very much possible and from your front end(if any) you can call the stored procedure as well. – Jacob Jun 19 '17 at 10:51
  • That's sound nice, but i have just no idea where to start. Can you give me some advice on the code writing ? – GanniH Jun 19 '17 at 11:01
  • @GanniH I have created a sample procedure, see my answer. – Jacob Jun 19 '17 at 11:03

3 Answers3

1

At first, you can't use DDL statement in trigger body as a open source, you should put it in execute immediate command. And also you should pay attention to user privileges which will execute then trigger, and role which will be granted to user, are there all priveleges granted, for create session, execute statements and so on. But if I were you I'll put user opening process in separate procedure, I think it won't be so simple code, so it will be easy to edit package procedure.

You can create context for you user sessions, wrap all your table where you want to control access into views and then filter view by user context. For example you table TAB_A with many rows, in table you store column ACS_USER and wrap table to V_TAB_A , when you can control access to table via view, all user access object will use views like

select * from V_TAB_A where ACSUSER = SYS_CONTEXT('USERENV','SESSION_USER')

Seyran
  • 711
  • 4
  • 8
  • so there isnt a proper way to create a specific user for a specific account right ? so, the question now is : there is another way to grant privileges to SPECIFIC ROW related to the username ? ( i dont want to grant permission to my USER and let him select * from my table. ) – GanniH Jun 19 '17 at 10:32
  • Control access by rows or columns is big theme to explain here in couple word, but you can see [this](https://docs.oracle.com/cd/B19306_01/network.102/b14266/accessre.htm#CHDDGEJG) and [this](http://docs.oracle.com/cd/B28359_01/network.111/b28531/vpd.htm#DBSEG007) – Seyran Jun 19 '17 at 12:36
0

Alternatively, you can use a database stored procedure instead of a trigger to do the DDL operations.

This is a pseudo code, make necessary changes as per your requirement. You can build your logic on top of this and if you are stuck, always post a question here in SO.

Table

CREATE TABLE account_info
(
   user_name   VARCHAR (20),
   user_password   VARCHAR (20) NOT NULL
);

Procedure

CREATE OR REPLACE PROCEDURE test_procedure (
   user_name       IN account_info.user_name%TYPE,
   user_password   IN account_info.user_password%TYPE)
AS
BEGIN
   INSERT INTO account_info (user_name, user_password)
        VALUES ('ABC', 'password123');

   -- check the user exists or not, if yes proceed

   EXECUTE IMMEDIATE
      'CREATE USER ' || user_name || ' IDENTIFIED BY ' || user_password;
-- do the rest of the activities such as grant roles, privilges etc.

END;
/
Alex Poole
  • 183,384
  • 11
  • 179
  • 318
Jacob
  • 14,463
  • 65
  • 207
  • 320
  • Oh god, thanks a lot! this looks kinda nice. I'm trying this right now, but i'm having the error ORA-00901 invalid CREATE command, which i assume that is related to an invalid create option, as the Oracle manual suggests me to correct the syntax. Any idea why ? Also in my table i have EMAIL_ACCOUNT that's a NOT NULL foreign key from USER ( the table that i use to storte name, surname, born date and sex ) and i've also inserted email into the function as EMAIL IN ACCOUNT.EMAIL_ACCOUNT%TYPE, but not inserted in the create user statement. is this wrong ? – GanniH Jun 19 '17 at 13:59
  • @GanniH - this is missing whitespace after `USER` and before `INDENTIFIED`, which is making the generated statement invalid. Try it with spaces added. – Alex Poole Jun 20 '17 at 09:04
0

The main problem I see here is grant to create user. You probably don't want your schema to be able to create users. So trigger (of course as other answers states this need to be execute immediate) shouldn't directly call create user. I would create procedure that create user in other schema than your working schema. That external schema would have grants to create user and your schema would have only grant to execute that one procedure from strong priviledged schema. In that case trigger will only call single procedure from external schema.

So to recap:

 CREATE OR REPLACE TRIGGER your_schema.NEW_USER
 AFTER INSERT ON ACCOUNT
 FOR EACH ROW
 BEGIN
   STRONG.CREATE_USER(:NEW.PASSWORD,:NEW.USERNAME);
 END;

CREATE OR REPLACE PROCEDURE STRONG.CREATE_USER(PASS VARCHAR2, USERNAME VARCHAR2) AS
DECLARE    
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  execute immediate 'CREATE USER ' || USERNAME || ' IDENTIFIED BY ' || PASS;
  execute immediate 'GRANT ROLE_NAME, CONNECT, RESOURCE TO ' || USERNAME; --and whatever user needs more
END;

Where STRONG user have rights to create user and your_schema has grant to execute STRONG.CREATE_USER

Additional thing. Never store passwords in plain text. Use some hash.

Kacper
  • 4,798
  • 2
  • 19
  • 34
  • Thank you so much for you help, Kacper! i tried this ( along with user75ponic solution ) but it gaves me the ORA-00901 invalid CREATE command, which i assume that is related to an invalid create option, as the Oracle manual suggests me to correct the syntax. Is this kind of syntax usable in Oracle 11g ? And also, what do you mean for hash the password ? thank in advance for your answers. – GanniH Jun 19 '17 at 14:01
  • Storing passwords in plain text is insecure. Standard practise is to use hash function on entry that user provides and compare it to result of hash function stored in db in that case if someone hacks your db passwords won't be revealed. Try that topic https://stackoverflow.com/questions/1054022/best-way-to-store-password-in-database – Kacper Jun 19 '17 at 14:29
  • Ok, i found the error that caused the ORA-00901, it was a space between the create command and the concat 'CREATE USER '^|| USERNAME || where i put the ^ symbol. now it seems to work as i tried to insert arbitrary values into the procedure ( without using the trigger ) and it worked ( EXAMPLE : 'CREATE OR REPLACE PROCEDURE CREATE_USER IS USER VARCHAR(20) := 'User1'; PASS VARCHAR(20) := 'Password1';' and it worked, but when i use the trigger it gave me ORA-04092: cannot COMMIT in a trigger. Any idead ? ( anyway hash sounds interesting, i'll give it a look, thank you. ) – GanniH Jun 19 '17 at 14:41
  • Yo can add to procedure `DECLARE PRAGMA AUTONOMOUS_TRANSACTION; ` but this is a bit nonsense as user will be created even if whole transaction is rolled back. Here solution by user75ponic seems better to insert and create user in one procedure. – Kacper Jun 19 '17 at 14:44
  • Yes, it worked like a charm, thank you very much Kacper! You think that a procedure would be better ? but i should call that procedure everytime i will insert row into ACCOUNT, for that a trigger would be better because create the USER automatically. Also, in the procedure i added EXECUTE IMMEDIATE 'GRANT SELECT ON ACCOUNT TO '||USERNAME, but oracle gave me the error ORA-00921: unexpected end of SQL command. Is possible to add select, delete and update permission in the procedure, and if yes, i need to create another procedure to show only the row related to the user ? thank you. – GanniH Jun 19 '17 at 15:00
  • My only concern is that scenario. 1. Insert row 2. Trigger create user. 3. rollback No user in table but user exist in system. Next time when you try insert same user you cannot create it and unique constraint will allow such creation. Except of that everything is fine. – Kacper Jun 19 '17 at 15:04
  • well, i will try to do some test and see if that works, its still an accademic project ahaha! anyway, now my concern is : is there a way to limit the view on the table for certain user ? i just created an user after my insert on ACCOUNT with GRANT SELECT ON ACCOUNT TO ||USERNAME, but this user can still see all the row in the database. Is there a way ( a procedure, a view ) to limit its access ? – GanniH Jun 19 '17 at 15:13
  • User can see all owned tables and all tables granted. It shouldn't see all data when you're logged to newly created user. – Kacper Jun 19 '17 at 15:44
  • indeed, i granted GRANT SELECT ON ACCOUNT TO NEWCREATEDUSER. but when granted, the select on account shows ALL THE RECORDS on the database. there is some way to restrict the views on the database to this specific user ? – GanniH Jun 19 '17 at 15:52
  • You would like to grant select row only? – Kacper Jun 19 '17 at 15:53
  • Yes, totally what i would want to, granting the access to USERS of their personal stored records ( so if user1 select * from email the results are only related to his email, and not to every email into my database. ) – GanniH Jun 19 '17 at 18:12
  • Try googling row level security. But this is not something you can get by standard grants. – Kacper Jun 20 '17 at 07:17