[.NET] Vreemd probleem met Entity Framework (gok ik)

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 25-05 11:39
Dag,

Ik heb een website draaien geschreven in ASP.NET. De website maakt via het Entity Framework (met MySQL provider) verbinding met een MySQL database, die overigens al een hele tijd draait. De users tabel in die database is al een tijd gevuld met accounts die mensen al een tijdje zonder problemen gebruiken.

Recent heb ik een nieuwe website geschreven die gebruik maakt van dezelfde database, ook via Entity Framework, alles op dezelfde manier zover ik kan vinden. Op deze nieuwe website krijgen sommige gebruikers (lang niet alle) ineens een error te zien tijdens het inloggen.


De exception die gegooid wordt komt maar op een plek voor in m'n code: tijdens het checken van de login gegevens. Het wachtwoord van de gebruiker (tijdens registratie) wordt aangevuld met een 64-karakter lange random salt en daarna met Sha256 gehashed. De salt + hash wordt daarna in de database opgeslagen als wachtwoord hash:
C#:
1
2
3
4
5
6
        public static string HashPassword(string password)
        {
            string salt = GetRandomSalt();
            string hash = Sha256Hex(salt + password);
            return salt + hash;
        }


Tijdens het inloggen wordt de input van de gebruiker op dezelfde manier gehashed en de twee hashes worden vergeleken. De salt (64 karakters die dus al aan de hash vastgeplakt zit) wordt daar dus eerst vanaf gehaald, waarna de hash van de input gevormd kan worden en vergeleken kan worden met de opgeslagen hash:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
        public static bool ValidatePassword(string password, string correctHash)
        {
            if (correctHash.Length < 128)
            {
                Log.Instance.LogError(new Exception("correctHash is not 128 chars. correctHash is '" + correctHash + "' and " +
                                      correctHash.Length + " chars."));
                return false;
            }
            string salt = correctHash.Substring(0, 64);
            string validHash = correctHash.Substring(64, 64);
            string passHash = Sha256Hex(salt + password);
            return string.Compare(validHash, passHash) == 0;
        }

Het eerste wat dus gebeurt is een check of de hash wel 128 karakters is (64 van de salt + 64 van de hash zelf). Voorheen werd hier een exception gegooid als dat niet zo was, en dat is precies de exception die getoond werd. Dat heb ik nu dus vervangen met een log van de hash en zijn lengte, om erachter te komen hoe het kan dat de hash niet 128 karakters is.

Het vreemde: als ik in de database kijk naar het account in kwestie dan is de hash gewoon netjes 128 karakters. Sterker nog: als ik zelf probeer in te loggen op zijn account, krijg ik de fout niet. Ik weet uiteraard zijn wachtwoord niet dus krijg gewoon te zien dat het wachtwoord verkeerd is, maar ik krijg niet de exception, de hash is dan netjes 128 karakters.


Met deze log heb ik nu kunnen zien dat 'correctHash' een lege string is. Op de een of andere manier worden zijn account details dus niet juist uit de database geladen en word zijn correctHash niet gezet, waar het dus fout gaat. Let op: in mijn geval ging het goed, met hetzelfde account op dezelfde live website!!

Om dit verder uit te zoeken heb ik nog een log toegevoegd, vlak voordat deze ValidatePassword functie aangeroepen wordt:
C#:
1
2
3
4
5
6
7
8
9
10
11
            User user = entities.users.FirstOrDefault(u => u.Custid == custid);

            // (user == null) check weg gelaten...

            Log.Instance.LogInfo(string.Format("User logging in: Id:{0}, Custid:{1}, Name:{2}, PwHash:{3}.", user.Id,
                                               user.Custid, user.Username, user.PasswordHash));

            if (!PasswordHash.ValidatePassword(password, user.PasswordHash))
            {
                // Invalid password...
            }


De eerste regel haalt via Entity Framework de user op met het ingevoerde 'custid' (customer id == username). De properties van het User object worden daarna gelogd (Id, CustId, Name en PasswordHash). Meteen daarna wordt de ValidatePassword functie aangeroepen, met dezelfde PasswordHash property op hetzelfde User object.

Je raadt het misschien al, maar de logging laat de volgende info zien:
code:
1
2
3
4
5
6
----------
12-10-2012 20:16:49|  Info:  User logging in: Id:112, Custid:94587, Name:<weg>, PwHash:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.

----------
12-10-2012 20:16:49|  Error: correctHash is not 128 chars. correctHash is '' and 0 chars.
System.Exception: correctHash is not 128 chars. correctHash is '' and 0 chars.


Bij de eerste logging komen de user details gewoon door, inclusief de PasswordHash van 128 karakters (in dit geval heb ik de hash vervangen door 'xxxxx...', het is een hash dus het zou in principe veilig moeten zijn, maar toch, uiteraard staat de juiste hash in de logging).

Nog geen seconde later is 'correctHash' ineens leeg. Dit is dezelfde PasswordHash property op hetzelfde User object. Het ene moment is de hash er, het andere moment niet meer.


Ik snap er niets meer van 8)7

Weet iemand waarom dit mis gaat? Ik verwacht dat het iets met Entity Framework te maken heeft, waarbij de properties niet op tijd geladen worden of iets dergelijks? Iets met lazy loading misschien? Ik kan het me niet voorstellen, want de hash word gewoon getoond in de logging, en de volgende regel is hij ineens leeg...

Help? |:(


EDIT
Inmiddels kan de gebruiker weer inloggen. Maar ik snap nog steeds niet wat er nu mis ging, laat staan waarom het nu ineens wel werkt. Ik verwacht dit probleem nog wel vaker tegen te komen (twee gebruikers hebben er nu last van gehad) dus ik zou graag weten wat er mis gaat...

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 17:02

Haan

dotnetter

Misschien niet direct een oplossing voor je probleem, maar waarom probeer je zelf het wiel opnieuw uit te vinden, terwijl dit al kant-en-klaar in ASP.NET zit?

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 25-05 11:39
Omdat ik veel extra functionaliteit nodig heb (integratie met een forum, via dat custid) dat ik niet klaar kreeg gespeeld met de ingebakken memberships. Accounts moeten bijvoorbeeld gekoppeld worden aan een account voor een (third party) forum waarbij een PM (via dat forum) dienst doet als verificatie dat de gebruiker ook echt de gebruiker is die ik moet hebben.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Nactive
  • Registratie: Juni 2011
  • Niet online
NIet direct een oplossing of antwoord op je vraag maar wel iets wat je kan proberen.

Slaag de string expliciet op in een String object voordat je uw functie aanroept.

Dus:

C#:
1
2
3
4
5
6
7
8
9
10
11
            User user = entities.users.FirstOrDefault(u => u.Custid == custid); 

            // (user == null) check weg gelaten... 
            string pwdHash = user.PasswordHash;
            Log.Instance.LogInfo(string.Format("User logging in: Id:{0}, Custid:{1}, Name:{2}, PwHash:{3}.", user.Id, 
                                               user.Custid, user.Username, pwdHash)); 

            if (!PasswordHash.ValidatePassword(password, pwdHash)) 
            { 
                // Invalid password... 
            }


In dit geval zou je toch echt niet meer mogen voorhebben dat de string plotseling `leeg is`.

De kans is wel zeer groot dat dit je probleem niet oplost want ik heb ook geen idee waar het aan kan liggen.

Acties:
  • 0 Henk 'm!

  • Motion2
  • Registratie: Maart 2006
  • Laatst online: 11:19
Heb je al geprobeerd om niet het gehele object op te halen maar alleen de hash?
Lazy lading heeft meer met gekoppelde tabellen te maken.

Heb je al geprobeerd om het object detached te maken? Het kan zijn dat je context sluit of wijzigt waardoor je object context wijzigt of verloopt.

Zie ook:

http://www.codeguru.com/c...-the-Entity-Framework.htm

[ Voor 17% gewijzigd door Motion2 op 13-10-2012 23:33 ]


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 25-05 11:39
Nactive: dat heb ik nu op deze manier live draaien, en tot zo ver lijkt het te werken want heb geen klachten meer gehad. Maar het is dus lastig te zeggen want het komt maar heel af en toe voor, het is gewoon afwachten ben ik bang...

Motion2: klinkt interessant, ik zal er eens naar kijken.

Bedankt beide.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 04-07 15:01
NickThissen schreef op vrijdag 12 oktober 2012 @ 22:27:
Omdat ik veel extra functionaliteit nodig heb (integratie met een forum, via dat custid) dat ik niet klaar kreeg gespeeld met de ingebakken memberships. Accounts moeten bijvoorbeeld gekoppeld worden aan een account voor een (third party) forum waarbij een PM (via dat forum) dienst doet als verificatie dat de gebruiker ook echt de gebruiker is die ik moet hebben.
In het standaard forms authentication systeem hebben alle gebruikers nog steeds gewoon een primary key in de vorm van de MembershipUser.ProviderUserKey property. Die kun je heel eenvoudig gebruiken om een relatie te bouwen naar een secundaire database table met extra gegevens om bijv. koppelingen naar software van derden tot stand te brengen.

Ik zie het probleem met 'extra functionaliteit' niet echt?

[ Voor 3% gewijzigd door R4gnax op 14-10-2012 14:43 ]


Acties:
  • 0 Henk 'm!

  • Sebazzz
  • Registratie: September 2006
  • Laatst online: 16:08

Sebazzz

3dp

Membership is altijd een hell qua testability. Daar valt wel omheen te schrijven natuurlijk, maar toch. Je zit sowieso vast aan een bepaald model dat niet flexibel genoeg is voor alle scenario's.

[Te koop: 3D printers] [Website] Agile tools: [Return: retrospectives] [Pokertime: planning poker]


Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Sebazzz schreef op zondag 14 oktober 2012 @ 19:36:
Membership is altijd een hell qua testability. Daar valt wel omheen te schrijven natuurlijk, maar toch.
Kwestie van netjes je calls naar Membership encapsuleren. Niet vanuit een mvc controller bijv rechtstreeks User aanroepen.

Het membership framework zelf hoef je niet te testen natuurlijk, dat doet MS al voor je.
Je zit sowieso vast aan een bepaald model dat niet flexibel genoeg is voor alle scenario's.
dat ben ik niet met je eens. Je moet idd niet het standaard profile gebruiken die MS zelf erbij "levert", dat is ruk. Maar gewoon data koppelen aan een guid is simpel.

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 04-07 15:01
Grijze Vos schreef op zondag 14 oktober 2012 @ 22:37:
[...]
Kwestie van netjes je calls naar Membership encapsuleren. Niet vanuit een mvc controller bijv rechtstreeks User aanroepen.
Wat dan gezien het bestaan Controller.User misschien weer vreemd overkomt. :)

Er is niets mis met de huidige IPrincipal ophalen en doorgeven aan onderliggende factories als deze data over de user nodig hebben (bijv. username, e-mail adres, etc). Waar het fout gaat, en waar Grijze Vos als ik hem een beetje ken eigenlijk op doelt, is logica die de security-aspecten van User aanspreekt overal doorheen gespekt hebben liggen. Het is essentieel dat je die logica verzegelt en afzondert om zaken testbaar te maken (en houden).

Encapsulatie van de Membership API zelf is daar de eerste stap in: een wrapper om Membership heen die het DI injecteerbaar maakt en de directe afhankelijkheid van de Membership en Roles globals weghaalt. Een tweede stap zou kunnen zijn om je authorisatie schema ( roles, permission sets, etc.) te encapsuleren in een decoratie op bijvoorbeeld je controller methods, om toegang tot bepaalde operaties te regelen. Een attribuut afgeleid van de AuthorizeAttribute class is de hiervoor door het framework reeds aangedragen oplossing.

Met de juiste mocks geinjecteerd is het autorisatie attribuut te testen en met een andere set kun je een simpele pass-through (dwz. altijd permissie geven) bewerkstelligen om de controller methods te testen. Easy!


offtopic:
Dit zal Grijze Vos heeeeeel bekend in de oren klinken, denk ik. ;)
Pagina: 1