Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent NullPointerException if any of the signing properties is null but signing isn't required #2268

Conversation

colindean
Copy link
Contributor

If a property such as signing.keyId was set to a null value, perhaps by setting it to the return of System.getenv('KEY_ID') when KEY_ID is unset, the PgpSignatoryFactory would throw an NPE when reading those properties while constructing the PgpSignatory.

This change adds a null check that also bypasses signatory creation when required is false.

Fixes #2267

@colindean colindean force-pushed the colindean/check-null-signing-properties-gh2267 branch from 74ab1ca to 4d40f11 Compare June 9, 2017 01:48
@eriwen
Copy link
Contributor

eriwen commented Jul 5, 2017

Hi @colindean. Thanks for the PR. Could you please confirm whether you've signed a Gradle CLA?

@colindean
Copy link
Contributor Author

I previously signed one with a previous employer, but I've just now signed it for myself, as whom this contribution was made.

@eriwen eriwen added cla:yes and removed cla:no labels Jul 19, 2017
Copy link
Contributor

@bmuschko bmuschko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for providing the pull request. Could you please address the review comments.

  • Can you please also add an integration test that demonstrates the change of behavior.
  • Maybe also add a note to the user guide that null values for the properties will throw an exception.

if(prop != null){
values.add(prop);
} else {
if(required){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is duplicated and should be reused. The else block below it uses the same logic.

Copy link
Contributor Author

@colindean colindean Jul 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception messages differ. Another concern was keeping the C/M/L on the exceptions thrown therein as close to the source of the problem as possible. If I extract this to a method like this:

private boolean throwIfRequired(boolean required, String message)

then I'll have to check the return value of the method in order to know if I should return early from the readProperties method.

I'm drawing a blank presently for ways to accomplish this more cleanly in Java.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made an attempt at refactoring in 1019328. It reads clearer but exposes some sentiment I captured in the commit message: this capability to retrieve a property safely probably belongs to Project as an overloaded method.

class PgpSignatoryFactorySpec extends Specification {

//https://github.com/gradle/gradle/issues/2267
protected Project aProjectWithNullProperties(){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method can just be static and without protected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ in 13032f2


class PgpSignatoryFactorySpec extends Specification {

//https://github.com/gradle/gradle/issues/2267
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use @Issue annotation to indicate the issue URL. The annotation should be added on the class level.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ in 9c421c7

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer. I wasn't aware that we still had other tests with the Spec suffix. We'll likely migrate them to Test at a later time.

import org.gradle.testfixtures.ProjectBuilder
import spock.lang.*

class PgpSignatoryFactorySpec extends Specification {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the code base we mainly follow the convention Test for test class names instead of Spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

org.gradle.plugins.signing.signatory.pgp.PgpKeyIdSpec was my reference for creating the test.

✅ in 346af98

Exception ex = thrown()
ex.class != NullPointerException
}
def "null properties throw a nice exception"() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test this for each of the properties. At the moment it will always fail on the first property. Let's use a where clause here + @Unroll. Let's also change "nice" into "human-readable" or "descriptive".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Descriptive ✅ in ece23b8

  • Where clause with @Unroll. I'll have to research how to do what you're asking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, you'll find plenty of examples in the Gradle core code base.

when:
factory.createSignatory(project, true)
then:
thrown InvalidUserDataException
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should verify the exception message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ in 4eba1fb

when:
factory.createSignatory(project, true)
then:
//notThrown NullPointerException // does not allow the real exception through
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should rather be noExceptionThrown().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's OK if an exception is thrown here, but we want it not to be an NPE. Wouldn't noExceptionThrown() cause all exceptions to fail the test?

If a property such as `signing.keyId` was set to a null value, perhaps by
setting it to the return of `System.getenv('KEY_ID')` when `KEY_ID` is unset,
the PgpSignatoryFactory would throw an NPE when reading those properties while
constructing the PgpSignatory.

This change adds a null check that also bypasses signatory creation when
`required` is false.

Fixes gradle#2267
@colindean colindean force-pushed the colindean/check-null-signing-properties-gh2267 branch from 4d40f11 to 4eba1fb Compare July 29, 2017 00:50
I feel like this should the responsibility of Project. A consumer should be
able to require that a property be present and not null and get exceptions
when the conditions are unmet.

From https://github.com/gradle/gradle/pull/2268/files#r130179732
@colindean
Copy link
Contributor Author

colindean commented Jul 29, 2017

Can you please also add an integration test that demonstrates the change of behavior.

Can you recommend an example integration test that I can use as inspiration for the right way to do it?

Maybe also add a note to the user guide that null values for the properties will throw an exception.

In what repo should I propose this change? I don't see the text of the linked guide anywhere in this repo.

Nevermind, my search string was too long for what was in subprojects/docs/src/docs/userguide/signingPlugin.xml literally. ✅ in f45d2bb.

TODO:

  • Integration test
  • Where clause with @Unroll in unit test

@bmuschko
Copy link
Contributor

Can you recommend an example integration test that I can use as inspiration for the right way to do it?

Have a look at SigningIntegrationSpec which extends AbstractIntegrationSpec. In this case you'll likely want to go with SigningIntegrationSpec as it already sets up some common infrastructure.

when:
factory.createSignatory(project, true)
then:
Exception ex = thrown()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use thrown(InvalidUserDataException).

when:
factory.createSignatory(project, true)
then:
//notThrown NullPointerException // does not allow the real exception through
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should check the actual exception type here. In fact I think we can delete this test case as it is covered by null properties throw a descriptive exception. Please also see my comment below on asserting the correct exception.

import org.gradle.api.InvalidUserDataException
import org.gradle.api.Project
import org.gradle.testfixtures.ProjectBuilder
import spock.lang.*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run "optimize imports" from the IDE here?

@bmuschko
Copy link
Contributor

bmuschko commented Aug 1, 2017

@colindean Thanks again for your fixes. I am going to finish off the PR. No more need for any changes from your end.

@bmuschko bmuschko merged commit 5eb7335 into gradle:master Aug 1, 2017
@colindean
Copy link
Contributor Author

Thank you! I would have liked to do more but I'm a little time limited. I was working on this during some funemployment!

@colindean colindean deleted the colindean/check-null-signing-properties-gh2267 branch August 1, 2017 18:48
@bmuschko bmuschko changed the title Adds a check for null values in signing properties Prevent NullPointerException if any of the signing properties is null but signing isn't required Aug 1, 2017
bmuschko added a commit that referenced this pull request Aug 1, 2017
@bmuschko bmuschko added this to the 4.2 RC1 milestone Aug 1, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants