Using oracle DBMS_LDAP to validate user credentials against Microsoft Active Directory - oracle

We are trying to authenticate our Application users using dbms_ldap against Microsoft AD and we have no means of testing it in our offshore environment
We have three specific questions
1)How to verify the user exists in Microsoft Active directory
We use the below code to get the Distinguished name of the Application user
DBMS_LDAP.USE_EXCEPTION := FALSE;
retval := DBMS_LDAP.search_s(ld => ldapSession,
base => LDAP_BASE,
scope => DBMS_LDAP.SCOPE_SUBTREE,
filter => '(&(objectclass=USER)(SAMAccountName=' ||
p_username || '))',
attrs => attrList,
attronly => 0,
res => ldapMessage);
-- get the DN
if retval <> DBMS_LDAP_UTL.SUCCESS THEN
RAISE l_ldap_exception;
END IF;
userDN := DBMS_LDAP.get_dn(ldapSession, ldapMessage);
So the first question is what will be the value of
userDN and ldapMessage
if the user doesn't exist in Microsoft AD
2)Suppose the user exists and has entered the wrong password in that case what will be the return value of retval
if p_password is null then
raise_application_error(-20000, 'Invalid Null password');
else
retval := DBMS_LDAP.simple_bind_s(ldapSession,userDN, p_password);
end if;
if retval <> DBMS_LDAP_UTL.SUCCESS THEN
RAISE l_ldap_exception;
and if;
3)My third question is suppose the user has logged in the system and ldapsession is still not unbind what will be the way to identify duplicate session

WARNING WARNING WARNING
This is less an answer and more a warning to anyone who uses the following code to validate a user.
retval := DBMS_LDAP.simple_bind_s(ldapSession, userDN, p_password);
By design (LDAP Design) this function call will always return success if the password is null. This was done to allow anonymous LDAP queries. This might be disabled on your specific server but not always.
So, if you want to use this function then make sure you wrap it with an if block that ensures the user provided password is not null. e.g.
if p_password is null then
raise_application_error(-20000, 'Invalid Null password');
else
retval := DBMS_LDAP.simple_bind_s(ldapSession,userDN, p_password);
end if;
For more details see: http://www.inside-oracle-apex.com/dbms_ldapsimple_bind_s-apex_ldapauthenticate-and-null-password/

First, read the docs. It has almost everything you need to answer your questions: http://docs.oracle.com/cd/E23943_01/oid.1111/e10186/dbmsldap_ref.htm
1) Either NULL or get_dn_exception will be raised, I'm not sure. Anyway, another method to check is to count the search results, where you only need to check if the count is >0:
DBMS_LDAP.count_entries(ld => ldapSession, msg => ldapMessage)
2) retval can only be DMBS_LDAP.SUCCESS, an exception is raised on any error
3) If you have session id, call any procedure that requires a valid LDAP session, e.g. simple_bind_s and check if invalid_session is raised. If you don't have a session id, I'm not aware of a method to determine if there are any LDAP sessions open.

In my application I do it like this:
ld := DBMS_LDAP.INIT(LDAP_SERVER, 389);
retval := DBMS_LDAP.SIMPLE_BIND_S(ld, LDAP_USER, LDAP_PASSWORD);
DBMS_LDAP.USE_EXCEPTION := FALSE;
retval := DBMS_LDAP.SEARCH_S(
ld => ld,
base => LDAP_BASE,
SCOPE => DBMS_LDAP.SCOPE_SUBTREE,
FILTER => '&(objectCategory=user)(sAMAccountName='||username ||')',
attrs => attrs,
attronly => 0,
res => ldapMessage);
retval := DBMS_LDAP.COUNT_ENTRIES(ld, ldapMessage);
ldapEntry := DBMS_LDAP.FIRST_ENTRY(ld, ldapMessage);
IF ldapEntry IS NULL THEN
retval := DBMS_LDAP.MSGFREE(ldapMessage);
retval := DBMS_LDAP.UNBIND_S(ld);
RAISE_APPLICATION_ERROR(-20001, 'User does not exist');
ELSE
userDN := DBMS_LDAP.GET_DN(ld, ldapEntry);
END IF;
retval := DBMS_LDAP.MSGFREE(ldapMessage);
IF p_password IS NULL THEN
retval := DBMS_LDAP.UNBIND_S(ld);
RAISE_APPLICATION_ERROR(-20000, 'Invalid Null password');
ELSE
retval := DBMS_LDAP.SIMPLE_BIND_S(ldapSession, userDN, p_password);
IF retval <> DBMS_LDAP_UTL.SUCCESS THEN
retval := DBMS_LDAP.UNBIND_S(ld);
RAISE_APPLICATION_ERROR(-20001, 'Wrong password');
END IF;
END IF;
retval := DBMS_LDAP.UNBIND_S(ld);
Regarding your questions:
1) How to verify the user exists in Microsoft Active directory
See my code
So the first question is what will be the value of userDN and ldapMessage
userDN will be NULL, ldapMessage I don't care.
2) Suppose the user exists and has entered the wrong password in that case what will be the return value of retval
For valid password: DBMS_LDAP.SUCCESS (0)
For wrong paasword: DBMS_LDAP.INVALID_CREDENTIALS (49)
Note, you get DBMS_LDAP.INVALID_CREDENTIALS only when you set DBMS_LDAP.USE_EXCEPTION := FALSE;, otherwise you get an exception if the password is wrong.
3) My third question is suppose the user has logged in the system and ldapsession is still not unbind what will be the way to identify duplicate session
Ensure proper calling of DBMS_LDAP.UNBIND_S(ld); in your code.
Another warning:
When you provide wrong password at DBMS_LDAP.SIMPLE_BIND_S(ldapSession, userDN, p_password); then the badPwdCount property in Active Directory is not incremented! Thus you can do infinite attempts to enter (or brute force) a password.

Related

Error: DPI-1059: bind variables are not supported in DDL statements when call procedure using node-oracledb

I created the stored procedure below in sql-developer :
CREATE OR REPLACE PROCEDURE CREATE_USER(
t_username IN VARCHAR2,
t_password IN VARCHAR2,
t_default_table IN VARCHAR2,
t_quota IN VARCHAR2
)
IS
m_statement VARCHAR2(1300);
m_username VARCHAR2(30) := t_username;
m_password VARCHAR2(30) := t_password;
m_default_table VARCHAR2(30) := t_default_table;
m_quota VARCHAR2(30) := t_quota;
BEGIN
m_statement := 'create user ' || t_username || ' identified by ' || t_password;
IF m_default_table != 'NULL' THEN
m_statement := m_statement || ' DEFAULT TABLESPACE ' || m_default_table;
END IF;
IF m_quota != '0' AND m_default_table != 'NULL' THEN
m_statement := m_statement || ' QUOTA ' || m_quota || 'M ON ' || m_default_table;
END IF;
EXECUTE IMMEDIATE (m_statement);
END;
And this compiled with no errors. I also connected to oracle. Then i got a user data (req.body) like this:
{ username: 'a', password: 'a', tablespace: 'NULL', quota: '0' }
But when i execute the 'call' procedure query:
oracle.getConnection(
{
uuser : "AN",
password: "AN123",
connectString: "localhost:1521/orcl"
},
(t_err, t_connection) => {
if(t_err){
console.error(t_err);
return;
}
t_connection.execute(
`BEGIN
createUser(:username, :password, :tablespace, :quota);
END;`,
{
username: req.body.username,
password: req.body.password,
tablespace: req.body.tablespace,
quota: req.body.quota,
},
(t_er, t_reslt) => {
if(t_er){
console.error(t_er);
return;
}
I received:
[Error: DPI-1059: bind variables are not supported in DDL statements] {
errorNum: 0,
offset: 0
}
I tried many ways to fix the procedure or the nodejs code but it didn't work. I'm very new to this topic. Can anyone help me, please?
You code runs without error for me - once I fix the conflict between the name in the SQL statement CREATE_USER and the name in the JS file createUser. Really make sure you are not calling some prior version of the PL/SQL package that does attempt to use bind variables in the CREATE USER statement (this was noted in comments). As the error states: this usage is not supported by Oracle DB. For example, you cannot do:
connection.execute(`create user :un identified by :pw`, ['cj2', 'cj2password']);
This is a DDL statement
It tries to use bind variables
And this isn't supported. And it fails with DPI-1059. Bind variables are used for data, not the text of statements.
Two other tips:
Filter or sanitize your input values to avoid SQL Injection attacks
Use the async/await style of programming (instead of callbacks) to save yourself some headaches. See all the node-oracledb examples.
The error code DPI-1059 looks unfamiliar, not like an Oracle error message. I've checked node-oracledb, they use NJS-000 to NJS-080. Googling lead to Oracle's Database Programming Interface for C, which defines this error code in dpiErrorMessages.h:
"DPI-1059: bind variables are not supported in DDL statements", // DPI_ERR_NO_BIND_VARS_IN_DDL
The constant DPI_ERR_NO_BIND_VARS_IN_DDL is only used once in the source code, in the file dpiStmt.c:
// attempt to improve message "ORA-01036: illegal variable name/number"
if (status < 0) {
if (error->buffer->code == 1036) {
if (stmt->statementType == DPI_STMT_TYPE_CREATE ||
stmt->statementType == DPI_STMT_TYPE_DROP ||
stmt->statementType == DPI_STMT_TYPE_ALTER)
dpiError__set(error, error->buffer->action,
DPI_ERR_NO_BIND_VARS_IN_DDL);
}
return DPI_FAILURE;
}
As Oracle's error messages are very confusing, it is worth looking into ORA-01036, for instance here

Oracle DBMS_DEBUG.GET_VALUE can't get variable values when debugging large packages

I try to debug some function in PL\SQL. When I debug large package (package body contains 21666 lines of code), I can't see variable values, I always get (Not a variable).
I added debug information.
When debuging smaller packages this problem don't ocures.
This is part of PL\SQL Developer debug log:
begin
handle.owner := :package_owner;
handle.name := :package_name;
if :inbody = 1 then
handle.namespace := sys.dbms_debug.namespace_pkg_body;
else
handle.namespace := sys.dbms_debug.namespace_pkgspec_or_toplevel;
end if;
:result := sys.dbms_debug.get_value(variable_name => :variable_name,
handle => handle,
scalar_value => :scalar_value,
format => :format);
if :result = 16 then :result := 3; end if;
end;
:RESULT = 3
:VARIABLE_NAME = requisition_no_
:SCALAR_VALUE = Null
:FORMAT = Null
:PACKAGE_OWNER = USER
:PACKAGE_NAME = PURCHASE_API
:INBODY = 1
error_no_such_object CONSTANT binary_integer := 3; -- no such var/parm
error_bad_handle CONSTANT binary_integer := 16; -- can't set bpt there
Anyone knows how to fix that ?
Maybe there is some Oracle Server configuration telling how large package body can be to have variable values on stack.

How to lock user account after multiple attempted and password must not be plain text in Oracle Apex?

I got a user table with column email, password, is_locked(varchar2(1) default = 'N'), login_failed(number default = 0). Below is the code which I write for my custom authentication scheme:
FUNCTION my_user_auth(
p_username in varchar2,
p_password in varchar2)
RETURN boolean
IS
l_result NUMBER := 0;
l_locked VARCHAR2(1) := 'N';
Sts NUMBER := 0;
v_return BOOLEAN := FALSE;
BEGIN
SELECT 1, USER_TYPE_ID, IS_LOCKED INTO l_result, Sts, l_locked
FROM USER_INFO
WHERE LOWER(EMAIL) = LOWER(p_username)
AND LOGIN_PASSWORD = p_password;
if (l_locked ='N' AND l_result = 1) then
APEX_UTIL.SET_SESSION_STATE('GLOBAL_STATUS_ITEM', Sts);
v_return := TRUE;
elsif (l_locked ='Y') then
apex_util.set_custom_auth_status (p_status => 'Account Locked, Exceeded Maximum Attempts! Please contact support staff.');
v_return := false;
end if;
RETURN v_return;
END my_user_auth;
Is that possible to make the user's email locked (change "is_locked" value to 'Y') after several failed attempt on login?
I had tried to add the code below but after added the code the user can't even login to the system even the password is correct:
if(l_result = 0) then
if(login_failed > 3) then
UPDATE USER_INFO
SET is_locked = 'Y'
WHERE email = p_username;
elsif
UPDATE USER_INFO
SET login_failed = login_failed + 1
WHERE email = p_username;
endif;
endif;
In addition, I got a form based on table which allow user to update their password, how can I set a constraint to make sure that the password insert is not plain text, e.g. included uppercase, special character and etc.
Thank you.
You can achieve this using JavaScript/Oracle Function. If you prefer to use JavaScript, i hope the below script helps you to achieve it assuming P1_PASSWORD is the variable the gets the input,add the below script on change (dynamic action).
var hasUpperCase = /[A-Z]/.test($v("P1_PASSWORD"));
var hasLowerCase = /[a-z]/.test($v("P1_PASSWORD"));
var hasNumbers = /\d/.test($v("P1_PASSWORD"));
var hasNonalphas = /\W/.test($v("P1_PASSWORD"));
if (hasUpperCase + hasLowerCase + hasNumbers + hasNonalphas < 3 || $v("P1_PASSWORD").length < 8 )
{
apex.message.clearErrors();
apex.message.showErrors([
{
type: apex.message.TYPE.ERROR,
location: ["inline"],
pageItem: "P1_PASSWORD",
message: "Password should contain Uppercase,lowercase,number and Special character greater than 8 characters",
unsafe: false
}
]);
}
For Oracle Function, you can refer to the Oracle Function available in the $ORACLE_HOME/rdbms/admin/utlpwdmg.sql location. Modify the function and create it as per your needs and you can use it for validation.
Hope this helps you.

Indy IdIRC - does not Join a Chat Room

I want to join an IRC room, using TIdIRC + Lazarus. I am familair with this discussion.
This is my code :
procedure TForm1.Button4Click(Sender: TObject);
begin
if Edit3.Text = '' then
begin
log('ERROR : Nick unknown, specify a Nick (look for the textbox saying ''Nick'' and replace it)', Memo3);
log('ERROR : Also provide Real Name and Alt Nic', Memo3);
Edit3.Text:= 'Nick';
Edit6.Text:= 'Real Name';
end
else
if Edit6.Text = '' then
begin
log('ERROR : Real Name unknown, provide Real Name', Memo3);
end
else
begin
IdIRC1.Host:=Edit1.Text;
IdIRC1.Port:=StrToInt(Edit5.Text);
IdIRC1.Nickname:=Edit4.Text;
IdIRC1.Username:=Edit4.Text;
IdIRC1.RealName:=Edit6.Text;
IdIRC1.AltNickname:=Edit7.Text;
log('Attempting to connect .. ', Memo1);
IdIRC1.Connect;
if IdIRC1.Connected then
begin
log('Connected ! ' , Memo1);
{Trying to join a channel .. }
if Edit4.Text = '' then
begin
IdIRC1.Join(Edit2.Text);
// IdIRC1.OnJoin:=;
log('channel joined', Memo1);
end
else
begin
IdIRC1.Join(Edit2.Text, Edit4.Text);
log('channel joined with password', Memo1);
end;
end
else
begin
end;
end;
begin
end;
end;
The log message "Connected !" is printed. Since this will happen after IdIRC1.Connect() is exited, and IdIRC1.Connected is found to be true, I am assuming that connection was established.
However, IdIRC1.Join() is not connecting.
Edit2.Text is set to '#Orcan'; while Edit4.Text is set to '' (an empty string).
I am recieving an error message:
Project has raised Exception class EIdConnClosedGracefully with the message :
Connection Closed gracefully
I am at the same time, from the same computer, logged in to irc.freenode.net , where the program is supposed to connect to, using konversations. In the user list the program, using the preset nick name, does not show up.
What am I doing wrong?

TNetSharingManager access violation problem

I'm trying to compile this project in Delphi 2010, which uses TNetSharingManager. I have imported the type library and tried compiling it, but unfortunately I'm getting an Access Violation in this function:
function TNetSharingManager.GetDefaultInterface: INetSharingManager;
begin
if FIntf = nil then
Connect;
Assert(FIntf nil, 'DefaultInterface is NULL. Component is not connected to Server. You must call "Connect" or "ConnectTo" before this operation');
Result := FIntf;
end;
(part of NETCONLib_TLB)
The error is in : if FIntf = nil then for some odd reason..
The code which is calling it:
procedure TForm1.GetConnectionList(Strings,IdList: TStrings);
var
pEnum: IEnumVariant;
vNetCon: OleVARIANT;
dwRetrieved: Cardinal;
pUser: NETCONLib_TLB.PUserType1;
NetCon : INetConnection;
begin
Strings.Clear;
IdList.Clear;
pEnum := ( NetSharingManager.EnumEveryConnection._NewEnum as IEnumVariant);
while (pEnum.Next(1, vNetCon, dwRetrieved) = S_OK) do
begin
(IUnknown(vNetCon) as INetConnection).GetProperties(pUser);
NetCon := (IUnknown(vNetCon) as INetConnection);
if (pUser.Status in [NCS_CONNECTED,NCS_CONNECTING])//remove if you want disabled NIC cards also
and (pUser.MediaType in [NCM_LAN,NCM_SHAREDACCESSHOST_LAN,NCM_ISDN] )
and (GetMacAddress(GuidToString(pUser.guidId))'' ) then
begin
//we only want valid network cards that are enabled
Strings.Add(pUser.pszwName );
IdList.Add(GuidToString(pUser.guidId));
end;
end;
end;
I don't understand why I cannot compare with nil. Any ideas?
It is likely the TNetSharingManager object itself has actually died (or wasn't created in the first place) when that error is triggered. The FIntF = nil expression is the first reference to an actual field of the class, i.e. it will be pointing into invalid address space.
[Edit] I download the source and followed the steps to import the TLB (Delphi 2010). To execute the appilcation, I had to (a) run Delphi as an admin, because I'm not a power user by default and (b) had to add a check for pUser <> nil because the final getProperties returns a nil-structure, but other than that the code run fine. So unfortunately, I can't seem to reproduce your problem.
Rereading your question, are you getting an AV while compiling?

Resources