Home > database >  Getting Parameter Size error for Oracle stored procedure C# .NET
Getting Parameter Size error for Oracle stored procedure C# .NET

Time:12-15

I'm new to PLSQL and can't figure out how to restive value from Oracle stored procedures. How can I solve this?

System.Exception: 'Parameter 'result': No size set for variable length data type: String.'

I'm using Iracle 10g

Procedure:

PROCEDURE LoginAuthentication(uid in users.userid%type,pass in users.password%type,result out users.role%type)
    is
    rl users.role%type;
    c number;
    begin
    
    select role into rl from users where userid=uid and password=pass and status=1;
    select count(*) into c from users where userid=uid and password=pass and status=1;
    if(c=1) then
    result:=rl;
    else
    result:='null';
    end if;
    exception
     when no_data_found then
     result:='null';
    end LoginAuthentication;

Code:

public string Login(String id, string password)
{
    using (OracleConnection oCon = new OracleConnection("DATA SOURCE = localhost:1521;USER ID = PROJECTDB;Password=pass"))
    {
        OracleCommand Cmd = new OracleCommand();
            
        try 
        {
            Cmd.Connection = oCon;
            Cmd.CommandText = " user_package.LoginAuthentication";
            Cmd.CommandType = CommandType.StoredProcedure;

            Cmd.Parameters.Add("uid", OracleType.VarChar).Value = id;
            Cmd.Parameters.Add("pass", OracleType.VarChar).Value = password;
            Cmd.Parameters.Add("result", OracleType.VarChar).Direction = ParameterDirection.Output;

            oCon.Open();
            Cmd.ExecuteNonQuery();

            string result = Cmd.Parameters["result"].Value.ToString();

            return result;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally 
        {
            oCon.Close();
        }
    }
}

Error:

System.Exception: 'Parameter 'result': No size set for variable length data type: String.

CodePudding user response:

The error explains what's wrong - you need to specify the parameter size. In Oracle and SQL Server text parameters require a size. For uid and pass the driver can assume the parameter size is equal to the value's size (which can result in truncated values), but with the output parameter, it has no way of knowing what size to use.

You could just pass the size but ....

Cmd.Parameters.Add("result", OracleType.VarChar,20)...

A far bigger problem though is the insecure password code. Passwords should never be stored as clear text, they should be salted and hashed multiple times (1000 at least).

And the word null isn't the same as the null value. Imagine what would happen if someone created a role named "null". This isn't nitpicking - systems have been cracked in just this way.

A more secure implementation

All .NET stacks going back to 2002 have a secure authentication and password storage feature. Don't roll your own if you can.

If you absolutely have to (why?) use the Rfc2898DeriveBytes class to hash passwords using a cryptographically strong algorithm.

It's as simple as :

var keygen = new Rfc2898DeriveBytes(pass, salt,1000);
var hash=k1.GetBytes(20);

And using that hash instead of a password. salt can be any random value. Typically this is stored along with the hash, ensuring every user account has a different salt. Quite often the salt and password are combined and store in a single field. When storing a new password, you could calculate a new salt and hash and store them together eg in a binary(28) field:

byte[] salt = new byte[8];
using (var rngCsp = new
RNGCryptoServiceProvider())
{
    rngCsp.GetBytes(salt);
}
var keygen = new Rfc2898DeriveBytes(pass, salt,1000);
var hash=keygen.GetBytes(20);

var finalHash=new byte[28];
Array.Copy(salt,finalHash,8);
Array.Copy(hash,finalHash,8,20);
...
//Store the hash

Password checking code seldom uses a COUNT(*) query too. The salt and hash for an account are loaded from the database, a new hash is calculated from user input and the two hashes compared.

The database code could be simplified too. using is safer than finally, as finally can be skipped in some catastrophic exceptions, while using won't. You could use a library like Dapper as well to eliminate the boilerplate code.

Your code could be simplified to this:

using (var oCon = new OracleConnection("DATA SOURCE = localhost:1521;USER ID = PROJECTDB;Password=pass"))
{
    var sql="select hash where userid=:id and status=1";
    var accountHash=oCon.SingleOrDefault<byte[]>(sql,new {id=id});


    if(accountHash == null)
    {
        //No such user
    }
    var salt=accountHash[0..8];
    var storedHash=accountHash[8..];

    var keygen = new Rfc2898DeriveBytes(pass, salt,1000);
    var inputHash=keygen.GetBytes(20);

    if(!inputHash.SequenceEqual(storedHash))
    {
        //Bad password
    }
}

It would be even better to use the built-in password storage feature of your stack though.

The query could be modified to return the role as well. Dapper can map query results to objects, so an Account class or record defined like this:

record Account(byte[] Hash,string Role);

Could be used in the query:

var sql="select Hash,Role where userid=:id and status=1";
var account=oCon.SingleOrDefault<Account>(sql,new {id=id});

CodePudding user response:

Panagiotis Kanavos has explained the error and that you should store the password as a salted hash. You can also improve your database code to eliminate the redundant query to COUNT the rows as your first query:

select role
into   rl
from   users
where userid=uid and password=pass and status=1;

Will fail with a NO_DATA_FOUND exception if there is not a matching row or a TOO_MANY_ROWS exception if there are duplicate rows so your second query:

select count(*)
into   c
from   users
where  userid=uid and password=pass and status=1;

Will only ever return a value of 1.

Therefore, your code can be simplified to:

PROCEDURE LoginAuthentication(
  uid    IN  users.userid%type,
  pass   IN  users.password%type,
  result OUT users.role%type
)
IS
BEGIN
  SELECT role
  INTO   result
  FROM   users
  WHERE  userid   = uid
  AND    password = pass
  AND    status   = 1;
EXCEPTION
  WHEN no_data_found THEN
    result := 'null';
END LoginAuthentication;

Further, your error-handling block will set the result to the string literal 'null'. You probably don't want to use a string literal and just want a NULL value.

EXCEPTION
  WHEN no_data_found THEN
    result := NULL;
END LoginAuthentication;
  • Related