Is there a way to automatically create a user in my application based on values in a table?
So I have a table named EMP as employee in that table I have attributes name, surname, username and password. What do I need to do to automatically create a user account in my application when a new employee is inserted into the database?
I have no idea where to start, is that done in processes or is there a pre-built feature that covers this?
Is there a sample code that everyone just uses and adapts to their needs?
In such a case, maybe it would be better to use custom authentication scheme instead of default, built-in Apex authentication.
How to create new users? Just insert them into the table. It would be a good idea to create a package which contains procedures used to manipulate with users' data.
You shouldn't store passwords as text for security reasons.
For example:
procedure p_create_user (p_name in varchar2, p_surname in varchar2,
p_username in varchar2, p_password in varchar2)
is
l_hash raw (2000);
begin
l_hash :=
dbms_crypto.hash (
utl_i18n.string_to_raw (p_password, 'AL32UTF8'),
dbms_crypto.hash_sh1);
insert into emp (name, surname, username, password)
values
(p_name, p_surname, p_username, l_hash);
end;
In Apex, authentication scheme requires you to create your own function (and set its name into the "Authentication Function Name" property) which accepts username/password combination and returns Boolean: TRUE (if credentials are valid) or FALSE (if not). Note that parameters' names MUST be p_username and p_password.
Use code similar to that in previous procedure:
function f_auth (p_username in varchar2, p_password in varchar2)
return boolean
is
l_pwd emp.password%type;
begin
select password
into l_pwd
from emp
where username = p_username;
return l_pwd = dbms_crypto.hash (
utl_i18n.string_to_raw (p_password, 'AL32UTF8'),
dbms_crypto.hash_sh1);
end;
That's all you need for basic functionality. You can add another procedure to let users change their password etc.
Related
i am trying to create a package called MSGG_SESSION with a procedure authenticate that accepts two VARCHAR2 parameters for username and password. i am suppose to put an package-private NUMBER variable for the current person ID.If "authenticate" matches a username and password in MSGG_USER , put the matching PERSON_ID in the new variable. Add a function get_user_id to the package that returns the value of the variable holding the person ID.
but i get two erros saying table or view does not exits starting at the second is before not_authenticated_exception
and sql statement ignored starting at priv_number varchar2(100).
CREATE OR REPLACE PACKAGE MSGG_SESSION IS
PROCEDURE AUTHENTICATE (USERNAME_to_auth IN VARCHAR2, PASSWORD_to_use IN VARCHAR2);
FUNCTION AUTHENTICATED_USER RETURN VARCHAR2;
END MSGG_SESSION;
/
create or replace package body msgg_session is
priv_number varchar2(100);
procedure authenticate (username_to_auth in varchar2, password_to_use in varchar2)
is
not_authenticated exception;
begin
select username
into priv_number
from user_password
where lower(username) = lower(username_to_auth)
and password = password_to_use;
exception
when no_data_found then
begin
raise not_authenticated;
exception
when not_authenticated then
raise_application_error(-20000, 'Not authenticated');
end;
when others then
raise;
end authenticate;
function authenticated_user
return varchar2
is
begin
return null;
end;
function get_user_id
return varchar2
is
begin
return priv_number;
end get_user_id;
end msgg_session;
/
You don't provide table DDL or the line number of the error message so it's not clear why you would get ORA-00942: table or view does not exist. Check the spelling of the table, make sure the table and the package are in the same schema and nothing is defined in double-quotes (e.g. user_password is not the same as "user_password").
Assuming that the table looks something like this:
create table user_password
( user_id integer constraint user_password_pk primary key
, username varchar2(30) not null constraint user_password_username_uk unique
, password varchar2(30) not null );
with sample test data:
insert into user_password (user_id, username, password)
values (1, 'ndubizuacn', 'Kittens');
A fixed version of your package would look like this:
create or replace package msgg_session as
procedure authenticate
( username_to_auth in user_password.username%type
, password_to_use in user_password.password%type );
function get_user_id
return user_password.user_id%type;
end msgg_session;
/
create or replace package body msgg_session as
priv_number user_password.user_id%type;
procedure authenticate
( username_to_auth in user_password.username%type
, password_to_use in user_password.password%type )
is
begin
select user_id into priv_number
from user_password
where lower(username) = lower(username_to_auth)
and password = password_to_use;
exception
when no_data_found then
raise_application_error(-20000, 'Not authenticated');
end authenticate;
function authenticated_user
return varchar2
is
begin
return null;
end authenticated_user;
function get_user_id
return user_password.user_id%type
is
begin
return priv_number;
end get_user_id;
end msgg_session;
/
Test:
begin
msgg_session.authenticate('ndubizuacn', 'Kittens');
dbms_output.put_line(msgg_session.get_user_id);
end;
/
Assuming dbms_output is enabled, this prints the value 1.
Using a global variable for something like this doesn't make a great interface, but it's a requirement of the assignment so I guess it shows how to use one. Same goes for needing to make two calls - perhaps you could expand your authenticated_user function to provide an alternative interface (pass in user and password, get back user_id all in one shot).
Storing passwords in plain text is an obvious security risk, and it is sometimes said that you should never use any online service that can send you your password if you forget it (you don't see that too often these days, but it used to be quite common). It would be more secure not to store the password at all but instead store ora_hash(upper(username)||'~'||password)), so for example for username ndubizuacn and password Kittens you would store 2160931220. Then your authentication function might be something like:
function authenticated_user
( username_to_auth in user_password.username%type
, password_to_use in user_password.password%type )
return user_password.user_id%type
is
l_user_id user_password.user_id%type;
begin
select user_id into l_user_id
from user_password
where username = username_to_auth
and password_hash = ora_hash(upper(username_to_auth)||'~'||password_to_use);
return l_user_id;
exception
when no_data_found then
raise_application_error(-20000, 'Not authenticated');
end authenticated_user;
I am using Oracle 12c and have a stored procedure that accepts 4 input fields, queries database using the same and return result set. However, these 4 input fields are user input values from front-end so user may or may not enter all 4 input fields. I would need to execute stored procedure with whatever values it receives and ignore the rest.
stored procedure definition:
procedure retrieve_data (
p_sn_no in integer,
p_name in varchar2,
p_city in varchar 2,
p_phone in integer,
return_msg out number)
is begin
select count(*) into return_msg from <table_name>
where sn_no=p_sn_no and name=p_name
and city=p_city and phone=p_phone
end
The requirement is to invoke the above stored procedure with any or all of the input parameters but how to prepare the select statement within the stored procedure with all or few input fields, something like select count(*) into return_msg from <table_name> where sn_no=p_sn_no instead of passing all 4 input fields?
Try this:
procedure retrieve_data
(return_msg out number,
p_sn_no in integer DEFAULT NULL,
p_name in varchar2 DEFAULT NULL,
p_city in varchar2 DEFAULT NULL,
p_phone in integer DEFAULT NULL)
is
begin
select count(*)
into return_msg
from
where sn_no = NVL(p_sn_no, sn_no)
and name = NVL(p_name, name)
and city = NVL(p_city, city)
and phone = NVL(p_phone, phone)
end
The stored procedure call still passes all input parameter values, only those that are not provided by the user will have NULL values, so your query should take advantage of that:
select count(*) into return_msg from <table_name>
where (sn_no=p_sn_no or p_sn_no is null)
and (name=p_name or p_name is null) -- etc.
...
FYI: Oracle 12c
I have created a custom type called Payeezy_Error:
create or replace TYPE PAYEEZY_ERROR
AS
OBJECT (
CODE VARCHAR(30),
DESCRIPTION VARCHAR(200)
);
And then in turn created a Table type of Payeezy_Errors:
create or replace TYPE PAYEEZY_ERRORS AS TABLE OF PAYEEZY_ERROR;
I then have a Procedure that takes Payeezy_Errors as an IN parameter:
create or replace PROCEDURE SAVE_USER_PAYMENT_TRANSACTION
(
in_AccountID IN VARCHAR2,
in_SequenceID IN VARCHAR2,
in_CorrelationID IN VARCHAR2,
in_TransactionID IN VARCHAR2,
in_TransactionTag IN VARCHAR2,
in_Currency IN VARCHAR2,
in_TransactionType IN VARCHAR2,
in_BankResponse IN VARCHAR2,
in_GatewayResponse IN VARCHAR2,
in_ValidationStatus IN VARCHAR2,
in_TransactionStatus IN VARCHAR2,
in_Errors IN PAYEEZY_ERRORS
)
AS
var_uptID NUMBER;
var_ErrorCount NUMBER := 0;
EX_AUTHENTICATION EXCEPTION;
BEGIN
-- Insert the Payeezy Response values tied to the user
INSERT INTO
USER_PAYMENT_TRANSACTION (
ACCOUNT_ID, UP_PAYMENT_SEQ_ID, CORRELATION_ID, TRANSACTION_ID,
TRANSACTION_TAG, CURRENCY, TRANSACTION_TYPE, BANK_RESPONSE,
GATEWAY_RESPONSE, VALIDATION_STATUS, TRANSACTION_STATUS
) VALUES (
in_AccountID, in_SequenceID, in_CorrelationID, in_TransactionID,
in_TransactionTag, in_Currency, in_TransactionType, in_BankResponse,
in_GatewayResponse, in_ValidationStatus, in_TransactionStatus
)
RETURNING
ID
INTO
var_uptID;
-- Insert any errors that may be associated with a failure/unsuccessful transaction
SELECT
COUNT(*)
INTO
var_ErrorCount
FROM
in_Errors;
IF (var_ErrorCount > 0) THEN
INSERT INTO
USER_PAYMENT_TRANSACTION_ERROR (
UPT_ID, CODE, DESCRIPTION
)
SELECT
var_uptID, e.CODE, e.DESCRIPTION
FROM
in_Errors;
END IF;
-- Exception Handling
EXCEPTION
WHEN EX_AUTHENTICATION THEN
raise_application_error(-20001, 'Authentication Failed.');
END SAVE_USER_PAYMENT_TRANSACTION;
When I compile the procedure it yells at me at the SELECT COUNT(*) statement that:
ORA-00942: table or view does not exist.
And red-dashes are under SELECT and in_Errors.
Further down the procedure I get the same error and the second INSERT INTO and in_Errors lines are red-dashes too.
I have exited and reloaded Oracle SQL Developer just to see if it was a caching thing. I have searched around the web but have not found my particular case.
If you want to use the table in a query, you'd need to use the table operator
SELECT
COUNT(*)
INTO
var_ErrorCount
FROM
table( in_Errors );
That works. But it means that you're taking all of the data that you have in the PL/SQL collection, moving it to the SQL VM, doing the aggregation, and then returning the result to PL/SQL. It would likely be more efficient (and less code) in this case to just do
var_ErrorCount := in_Errors.count;
I would like to know how to use another table for authenticating.
As you can see I've got a table named 'vdv_medewerker', this table contains two columns, -gebruikersnaam- & -wachtwoord-. These fields are being validated on login, and it works.
Now what I would like to do is, I would like this authenticating script to use values from another table named 'vdv_klant', this table also includes a -gebruikersnaam- & -wachtwoord- column.
I've tried finding a solution online, but I didn't succeed.
CREATE OR REPLACE FUNCTION
custom_inlog(p_username IN VARCHAR2, p_password IN VARCHAR2)
RETURN BOOLEAN
AS
v_gebruikersnaam varchar2(30);
v_wachtwoord varchar2(30);
BEGIN
SELECT gebruikersnaam, wachtwoord
INTO v_gebruikersnaam, v_wachtwoord
FROM vdv_medewerker
WHERE UPPER(gebruikersnaam) = UPPER(p_username)
AND wachtwoord = p_password;
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
Presumably your APEX authentication scheme has been set up to call your function above? So you can either just change your function to look at the new table, or you can create a new function based on the new table and change the APEX authentication scheme to call that instead of the old one:
To give an answer after reading the comments you gave in the meantime, here is a working version for your code:
CREATE OR REPLACE FUNCTION
custom_inlog(p_username IN VARCHAR2, p_password IN VARCHAR2)
RETURN BOOLEAN
AS
v_gebruikersnaam varchar2(30);
v_wachtwoord varchar2(30);
BEGIN
SELECT gebruikersnaam, wachtwoord
INTO v_gebruikersnaam, v_wachtwoord
FROM (
SELECT gebruikersnaam, wachtwoord FROM vdv_medewerker
UNION
SELECT gebruikersnaam, wachtwoord FROM vdv_klant
)
WHERE UPPER(gebruikersnaam) = UPPER(p_username)
AND wachtwoord = p_password;
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
But in the longrun i would suggest using a predfined view and imrpove your database structure in the future.
I know it seems like a basic thing, but I've never done this before.
I'd like to return a single record from an existing table as the result of an Oracle PL/SQL function. I've found a few different ways of doing this already, but I'm interested in the best way to do it (read: I'm not all that happy with what I've found).
The jist of what I am doing is this... I have a table called 'users', and I want a function 'update_and_get_user' which given a UserName (as well as other trusted information about said user) will potentially perform various actions on the 'users' table, and then return either zero or one row/record from said table.
This is the basic outline of the code in my head at the moment (read: no idea if syntax is even close to correct):
CREATE FUNCTION update_and_get_user(UserName in VARCHAR2, OtherStuff in VARCHAR2)
RETURN users PIPELINED IS
TYPE ref0 IS REF CURSOR;
cur0 ref0;
output_rec users%ROWTYPE;
BEGIN
-- Do stuff
-- Return the row (or nothing)
OPEN cur0 FOR 'SELECT * FROM users WHERE username = :1'
USING UserName;
LOOP
FETCH cur0 INTO output_rec;
EXIT WHEN cur0%NOTFOUND;
PIPE ROW(output_rec);
END LOOP;
END update_and_get_user;
I've seen examples where a record or table is returned, the type of record or table having been created / declared beforehand, but it seems like if the table has already been defined, I should be able to utilize that, and thus not have to worry about syncing the type declaration code if table changes are ever made.
I'm open to all potential solutions and commentary, but I do really want to keep this in a single PL/SQL function (as opposed to code in some other language / framework that communicates with the database multiple times, finishing with some form of 'SELECT * FROM users WHERE username=blah') as the system calling the function and the database itself may be different cities. Outside of that limit, I'm open to changing my thinking.
This is how I would do it. Variables/table-names/column-names are case-insensitive in Oracle, so I would use user_name instead of UserName.
CREATE TABLE users( UserName varchar2(20), OtherStuff VARCHAR2(20) );
Function update_and_get_user. Note that I return a ROWTYPE instead of Pipelined Tables.
CREATE OR REPLACE FUNCTION update_and_get_user(
in_UserName IN users.UserName%TYPE,
in_OtherStuff IN users.OtherStuff%TYPE )
RETURN users%ROWTYPE
IS
output_rec users%ROWTYPE;
BEGIN
UPDATE users
SET OtherStuff = in_OtherStuff
WHERE UserName = in_UserName
RETURNING UserName, OtherStuff
INTO output_rec;
RETURN output_rec;
END update_and_get_user;
And this is how you would call it. You can not check a ROWTYPE to be NULL, but you can check username for example.
DECLARE
users_rec users%ROWTYPE;
BEGIN
users_rec := update_and_get_user('user', 'stuff');
IF( users_rec.username IS NOT NULL ) THEN
dbms_output.put_line('FOUND: ' || users_rec.otherstuff);
END IF;
END;
A solution using PIPED ROWS is below, but it doesn't work that way. You can not update tables inside a query.
SELECT * FROM TABLE(update_and_get_user('user', 'stuff'))
ORA-14551: cannot perform a DML operation inside a query
Solution would look like that:
CREATE OR REPLACE TYPE users_type
AS OBJECT
(
username VARCHAR2(20),
otherstuff VARCHAR2(20)
)
CREATE OR REPLACE TYPE users_tab
AS TABLE OF users_type;
CREATE OR REPLACE FUNCTION update_and_get_user(
in_UserName IN users.username%TYPE,
in_OtherStuff IN users.otherstuff%TYPE )
RETURN users_tab PIPELINED
IS
output_rec users%ROWTYPE;
BEGIN
UPDATE users
SET OtherStuff = in_OtherStuff
WHERE UserName = in_UserName
RETURNING username, otherstuff
INTO output_rec;
PIPE ROW(users_type(output_rec.username, output_rec.otherstuff));
END;