Maison> Java> javaDidacticiel> le corps du texte

Pièges liés à l'autorisation : que dissimule Keycloak ?

WBOY
Libérer: 2024-07-19 17:25:20
original
469 Les gens l'ont consulté

Author: Valerii Filatov

User authorization and registration are important parts of any application, not only for users but also for security. What pitfalls does the source code of a popular open-source identity management solution hide? How do they affect the application?

Authorization pitfalls: what does Keycloak cloak?

If you've ever implemented authorization for web apps, you probably know all the frustrating issues that can arise. I'm no exception, either.

Once, I implemented the messenger-based authorization in the frontend part of one project. It seemed like the world's easiest task but turned out to be the opposite: deadlines were looming, code was tripping over messenger APIs, and people around me were yelling.

After this case, my colleague showed me a cool tool that would streamline the implementation of authorization in our future projects. This project was Keycloak, an open-source Java solution to enable single sign-on (SSO) with identity and access management aimed at modern apps and services.

As I use the solution myself, I find it interesting to get into the source code and use the PVS-Studio static analyzer to look for the bugs hidden there.

He-he, classic NullPointerException

Someone knocked on the door. The Junior Dev tried to open a door and crashed. "NullPointerException!" concluded the Senior Dev.

Errors related to checking fornullare encountered in almost every project we've checked before. So, let's start with this old but gold error.

Fragment N1

private void checkRevocationUsingOCSP(X509Certificate[] certs) throws GeneralSecurityException { .... if (rs == null) { if (_ocspFailOpen) logger.warnf(....); else throw new GeneralSecurityException(....); } if (rs.getRevocationStatus() == OCSPProvider.RevocationStatus.UNKNOWN) { // <= if (_ocspFailOpen) logger.warnf(....); else throw new GeneralSecurityException(....); .... }
Copier après la connexion

Warning:

V6008 Potential null dereference of 'rs'.

This code snippet has anullcheck, but we need to understand what's going on inside. If the_oscpFailOpenvariable is true, the program won't throw an exception. It'll only save the log about it and continue execution—it receives aNullPointerExceptionin the followingif, since thersvariable has already been used insideif.

It might seem like the program would just throw another exception if there weren't theNullPointerException. But that's not the case because the_oscpFailOpenvariable is true, and the program should continue execution. No fate, it tripped over thenullpointer and fell into the exception.

Fragment N2

public void writeDateAttributeValue(XMLGregorianCalendar attributeValue) throws ProcessingException { .... StaxUtil.writeAttribute( writer, "xsi", JBossSAMLURIConstants.XSI_NSURI.get(), "type", "xs:" + attributeValue.getXMLSchemaType().getLocalPart() // <= ); if (attributeValue == null) { StaxUtil.writeAttribute( writer, "xsi", JBossSAMLURIConstants.XSI_NSURI.get(), "nil", "true" ); .... }
Copier après la connexion

Warning*:*

V6060 The 'attributeValue' reference was utilized before it was verified against null.

"Better late than never," this phrase is good enough for many cases, but unfortunately, not for thenullcheck. In the above code snippet, theattributeValueobject is used before it's checked for existence, which leads to theNullPointerException.

Note. If you want to check out other examples of such bugs, we've put together a list for you!

Arguments?

I don't know how it has happened, but Keycloak has errors related to the number of arguments in the format string functions. It isn't an illustrative statistic—just a fun fact, though.

Fragment N3

protected void process() { .... if (f == null) { .... } else { .... if (isListType(f.getType()) && t instanceof ParameterizedType) { t = ((ParameterizedType) t).getActualTypeArguments()[0]; if (!isBasicType(t) && t instanceof Class) { .... out.printf(", where value is:\n", ts); // <= .... } } else if (isMapType(f.getType()) && t instanceof ParameterizedType) { .... out.printf(", where value is:\n", ts); // <= .... } } }
Copier après la connexion

Warning:

V6046 Incorrect format. A different number of format items is expected. Arguments not used: 1.

In the above fragment, when theprintffunction is called, we pass a format string and a value to be substituted into the string. There's only one problem: there's simply no place in the string to substitute arguments.

It's pretty interesting that a dev not only copied and pasted both the code fragment fromiftoelse if, but a dev also copied and pasted the error insideelse if.

In the next snippet, we have the opposite case: developers have spared arguments.

Fragment N4

public String toString() { return String.format( "AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, tabId=%s, clientUUID=%s, authNotesFragment=%s ]", authSessionId, clientUUID, authNotesFragment); // <= }
Copier après la connexion

Warning:

V6046 Incorrect format. A different number of format items is expected. Missing arguments: 4.

Although devs passed theauthSessionId,clientUUIDandauthNotesFragmentarguments to the function, the fourthtabldargument is a bit missed.

In this case, theString.formatmethod will throw anIllegalFormatException, which can be a nasty surprise.

Unclean code

The following PVS-Studio warnings aren't related to code health errors. These warnings are more about how high-quality the code is. I just pointed out that these aren't firm errors.

"What's the point of looking at them?" you may think. First, I believe that code should be clean and neat. It's not a matter of tastes: clean code is not only about the visual experience but also about code readability. Imho, it's important for any project, especially Open Source. Secondly, I'd like to show that the PVS-Studio static analyzer helps not only fix errors in code but also makes it beautiful and clean.

Not enough variables, My Lord!

For some reason, the following code fragment looks in my mind like an evil plan of someone who has a bad attitude to use variables: "Let's adopt variables and leave them waiting in horror for the garbage collector to gobble them up..."

Fragment N5

private void onUserRemoved(RealmModel realm, String userId) { int num = em.createNamedQuery("deleteClientSessionsByUser") .setParameter("userId", userId).executeUpdate(); // <= num = em.createNamedQuery("deleteUserSessionsByUser") .setParameter("userId", userId).executeUpdate(); }
Copier après la connexion

Warning:

V6021 The value is assigned to the 'num' variable but is not used.

From the point of program operation, nothing terrible happens in this code snippet. But it's still not clear why developers wrote that.

Thenumvariable contains the number of set parameters that theexecuteUpdatemethod returns. So, I thought that the method might have had a check for changes. Even if I rewind in time, I'll only find that calls to the method aren't written to a variable but accept the current state a little later.

The result is a useless assignment to the variable—just like in the next fragment.

Fragment N6

private void verifyCodeVerifier(String codeVerifier, String codeChallenge, String codeChallengeMethod) throws ClientPolicyException { .... String codeVerifierEncoded = codeVerifier; try { if (codeChallengeMethod != null && codeChallengeMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) { codeVerifierEncoded = generateS256CodeChallenge(codeVerifier); } else { codeVerifierEncoded = codeVerifier; // <= } } catch (Exception nae) { .... } }
Copier après la connexion

Warning:

V6026 This value is already assigned to the 'codeVerifierEncoded' variable.

If you look at the code, you can see that before this, thecodeVerifierEncodedvariable has already assigned the same value in theelsebranch. A developer just did redundant action: and useless, and overcomplicating.

The next code fragment just amuses me.

Fragment N7

private static Type[] extractTypeVariables(Map typeVarMap, Type[] types){ for (int j = 0; j < types.length; j++){ if (types[j] instanceof TypeVariable){ TypeVariable tv = (TypeVariable) types[j]; types[j] = typeVarMap.get(tv.getName()); } else { types[j] = types[j]; // <= } } return types; }
Copier après la connexion

Warning:

V6005 The variable 'types[j]' is assigned to itself.

It looks just like the previous snippet, but honestly, I'm totally lost on what this code is trying to do.

At first, I thought we were facing a nested loop here, and the author had just mixed up the variablesiandj. But eventually I realized that there's only one loop here.

I also thought the assignment appeared when developers were refactoring the code, which might have been even more complicated before. In the end, I found that the function was originally created this way (commit).

So sweet copy-paste

Copy-paste errors are quite common. I'm sure you've seen them even in your own code.

Keycloak has some traces of the copy-paste use, too.

Fragment 8

public class IDFedLSInputResolver implements LSResourceResolver { .... static { .... schemaLocations.put("saml-schema-metadata-2.0.xsd", "schema/saml/v2/saml-schema-metadata-2.0.xsd"); schemaLocations.put("saml-schema-x500-2.0.xsd", "schema/saml/v2/saml-schema-x500-2.0.xsd"); schemaLocations.put("saml-schema-xacml-2.0.xsd", "schema/saml/v2/saml-schema-xacml-2.0.xsd"); schemaLocations.put("saml-schema-xacml-2.0.xsd", "schema/saml/v2/saml-schema-xacml-2.0.xsd"); // <= schemaLocations.put("saml-schema-authn-context-2.0.xsd", "schema/saml/v2/saml-schema-authn-context-2.0.xsd"); .... } .... }
Copier après la connexion

Warning*:*

V6033 An item with the same key '"saml-schema-xacml-2.0.xsd"' has already been added.

Honestly, even though I knew there was a typo in the source code, I had a hard time finding it right away in the code.

If you notice, in theschemaLocations.putmethod calls, the passed arguments are quite similar. So, I assume that the dev who wrote this code simply copied a string as a template and then just changed values. The problem is that during copy-pasting, one line that repeats the previous one has crept into the project.

Such "typos" can either lead to serious consequences or have no effect at all. This copy-pasting example has been in the project since November 21, 2017 (commit), and I don't think it causes any serious problems.

We're including this error in the article to remind developers to be careful when copying and pasting code fragments and to keep an eye on any changes in code. Want to read more about it? Here's the article about the copy-paste typos.

Unreachable code

The headline gives us a little clue as to what kind of warning awaits us in the following snippet. I suggest you use your detective skills to spot the flaw yourself.

Fragment N9

public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { switch (context.getEvent()) { case REGISTER: case UPDATE: .... case RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST: .... executeOnAuthorizationRequest(ropcContext.getParams()); return; default: return; } }
Copier après la connexion

It's not that easy to detect, isn't it? I'm not gloating over you, I just give you a chance to roughly access the situation. I show it because a small flaw makes it harder for the developer to find the error without examining the rest of the code. To find out what error is lurking in this code snippet, we need to look at what is cloaked in theexecuteOnAuthorizationRequestmethod:

private void executeOnAuthorizationRequest(MultivaluedMap params) throws ClientPolicyException { .... throw new ClientPolicyException(....); }
Copier après la connexion

Yes, this method throws an exception. That is, all the code written after calling this method will be unreachable—the PVS-Studio analyzer detected it.

public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException { switch (context.getEvent()) { case REGISTER: case UPDATE: .... case RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST: .... executeOnAuthorizationRequest(ropcContext.getParams()); return; // <= default: return; } }
Copier après la connexion

Warning:

V6019 Unreachable code detected. It is possible that an error is present.

Even if this flaw is quite small, a similar case could lead to more serious consequences. I can only note here that a static analyzer will help you avoid such unpleasant things.

I dictate CONDITION

Now, let's look at conditional statements and cases when they execute their code.

Fragment N10

public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData, boolean clearUser) { .... if (password == null || password.isEmpty()) { return badPasswordHandler(context, user, clearUser,true); } .... if (password != null && !password.isEmpty() && // <= user.credentialManager() .isValid(UserCredentialModel.password(password))) { .... } }
Copier après la connexion

Warning:

V6007 Expression 'password != null' is always true.

V6007 Expression '!password.isEmpty()' is always true.

Here are two warnings in one line! What does the analyzer warn us about? The first conditional statement checks that the password is non-nulland non-empty. If the opposite occurs, the function is no longer executed. In the line the analyzer highlighted, both of these checks are repeated, so the conditions are always true.

On the one hand, it's better to check than not to check. On the other, such duplicates may lead to missing the part of the condition that may be equal tofalse—it can really affect the program operation.

Let's repeat the exercise.

Fragment N11

public KeycloakUriBuilder schemeSpecificPart(String ssp) throws IllegalArgumentException { if (ssp == null) throw new IllegalArgumentException(....); .... if (ssp != null) // <= sb.append(ssp); .... }
Copier après la connexion

Warning:

V6007 Expression 'ssp != null' is always true.

In general, the case is the same. If thesspvariable isnull, the program throws an exception. So, the condition below isn't only true all the time but is also redundant because the corresponding code block will always be executed.

The condition in the next fragment is also redundant.

Fragment N12

protected String changeSessionId(HttpScope session) { if (!deployment.turnOffChangeSessionIdOnLogin()) return session.getID(); // <= else return session.getID(); }
Copier après la connexion

Warning:

V6004 The 'then' statement is equivalent to the 'else' statement.

In this method, the same code is executed under different seasons, the moon phases, and, most importantly, under different conditions. So, again, the condition is redundant here.

Digging into the code, I found the code snippet that is like two drops of water similar to the one above:

protected String changeSessionId(HttpSession session) { if (!deployment.turnOffChangeSessionIdOnLogin()) return ChangeSessionId.changeSessionId(exchange, false); else return session.getId(); }
Copier après la connexion

As you can see, when the condition is executed, the method that changes the session ID is called.

So, we can make two guesses: either devs just copied the code and changed the condition result, or the first condition still should have updated the session, and this error goes way beyond the "sloppy" code.

But we mustn't live by redundant conditions alone*!*

Fragment N13

static String getParameter(String source, String messageIfNotFound) { Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source); while (matcher.find()) { return matcher.group(1).replaceAll("'", ""); // <= } if (messageIfNotFound != null) { throw new RuntimeException(messageIfNotFound); } return null; }
Copier après la connexion

Warning:

V6037 An unconditional 'return' within a loop.

I have a feeling thiswhile *was raised by *ifs. This code may have some hidden intentions, but the analyzer and I see the same thing here: a loop that always performs one iteration.

The code author might have wanted a different behavioral outcome. Even if they don't want, we'll find this code a bit harder to understand than if there is just a conditional operator.

String comparison

In the following snippet, a developer suggests everything is so easy. But it turns out it's not.

Fragment N14

public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key key = (Key) o; if (action != key.action) return false; // <= .... }
Copier après la connexion

Warning:

V6013 Strings 'action' and 'key.action' are compared by reference. Possibly an equality comparison was intended.

Comparing strings implies that we compare their contents. We actually compare object references. This also applies to arrays and collections, not only strings. I think it's clear that in certain cases, code operations can lead to unexpected consequences. Most importantly, it's pretty easy to fix such an error:

public boolean equals(Object o) { .... if (!action.equals(key.action)) return false; .... }
Copier après la connexion

Theequalsmethod returns a comparison exactly to the contents of two strings. Victory!

I'll draw your attention to the fact that the static analyzer has detected the error, which a developer would most likely have missed when reviewing the code. You can read about this and other reasons for using static analysis in this article.

Double-checked locking

Double-checked locking is a parallel design pattern used to reduce the overhead of acquiring a lock. We first check the lock condition without synchronization. If it's encountered, the thread tries to acquire the lock.

If we streamline it, this pattern helps get the lock only when it's actually needed.

I think you've already guessed that there are bugs in the implementation of this template as I've started talking about it. Actually, they are.

Fragment N15

public class WelcomeResource { private AtomicBoolean shouldBootstrap; .... private boolean shouldBootstrap() { if (shouldBootstrap == null) { synchronized (this) { if (shouldBootstrap == null) { shouldBootstrap = new AtomicBoolean(....); } } } return shouldBootstrap.get(); }
Copier après la connexion

Warning:

V6082 Unsafe double-checked locking. The field should be declared as volatile.

The analyzer warns that theshouldBootstrapfield doesn't have thevolatilemodifier. What does it affect? In such code, it's likely that different threads use an object until they're fully initialized.

This fact doesn't seem to be that significant, does it? In the next example, the compiler may change the action order with non-volatilefields.

Fragment N16

public class DefaultFreeMarkerProviderFactory implements FreeMarkerProviderFactory { private DefaultFreeMarkerProvider provider; // <= .... public DefaultFreeMarkerProvider create(KeycloakSession session) { if (provider == null) { synchronized (this) { if (provider == null) { if (Config.scope("theme").getBoolean("cacheTemplates", true)) { cache = new ConcurrentHashMap<>(); } kcSanitizeMethod = new KeycloakSanitizerMethod(); provider = new DefaultFreeMarkerProvider(cache, kcSanitizeMethod); } } } return provider; } }
Copier après la connexion

Warning:

V6082 Unsafe double-checked locking. The field should be declared as volatile.

Warum beheben Entwickler diese Codefragmente nicht, wenn der Fehler so gefährlich ist? Der Fehler ist nicht nur gefährlich, sondern auch heimtückisch. Die meiste Zeit funktioniert alles so, wie es sollte. Es gibt viele verschiedene Gründe, warum schlechtes Verhalten auftreten kann, beispielsweise aufgrund der verwendeten JVM oder der Thread-Scheduler-Operationen. Daher kann es ziemlich schwierig sein, die Fehlerbedingungen zu reproduzieren.

Mehr über solche Fehler und deren Gründe können Sie im Artikel lesen.

Abschluss

Am Ende dieses Artikels möchte ich darauf hinweisen, dass ich den Analysator etwas nicht ordnungsgemäß verwendet habe. Ich wollte Sie nur unterhalten und Ihnen die Fehler im Projekt zeigen. Um Fehler zu beheben und ihnen vorzubeugen, ist es jedoch besser, den statischen Analysator regelmäßig beim Schreiben von Code zu verwenden. Mehr darüber können Sie hier lesen.

Der Analysator hat uns jedoch dabei geholfen, verschiedene Fehler zu erkennen, die sowohl mit dem Programmbetrieb als auch mit unzureichender Codesauberkeit zusammenhängen (ich halte das immer noch für wichtig, und Sie werden meine Meinung kaum ändern). Fehler sind nicht das Ende der Welt, wenn man sie rechtzeitig erkennt und behebt. Dabei hilft die statische Analyse. Probieren Sie PVS-Studio aus und prüfen Sie Ihr Projekt damit kostenlos.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!