-
Notifications
You must be signed in to change notification settings - Fork 90
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
Converts storage and secure area to asynchronous APIs #856
Conversation
* obtain a [DocumentStore] lock by running the code that accesses these APIs inside | ||
* [DocumentStore.withLock] (or a convenience shortcut [Document.withLock]). Note that | ||
* there is a single lock for all documents and credentials in a [DocumentStore] | ||
* instance and only a single coroutine can hold the lock at the same time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to look at this in more detail but my gut feeling is that it's not appropriate to ask API callers to obtain a lock before calling an API. And it's also not very common to have this requirement. I don't think we should require this, it's a bit of an anti-pattern.
The ordinary way this is done - and the way I thought we'd do it when you mentioned a DocumentStore-wide lock - is that the API entry-points the app calls will take the lock. Is there a specific reason we can't do that instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't disagree with the general gist of it, but there is a lot of nuance here. Let's move this to the chat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, removed the locking altogether for now. We'll need to figure out how to do it later.
e2769df
to
46a0dbf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a read-through. Looks great and thanks for all your hard work on this! Comments included but my only real question I have is that we're not entirely consistent with asynchronous object creation...
In some cases the constructors are still public and for the Credential hierarchy you expect the user to call the constructor and then a suspend function after that. I think in general we should move to suspend create()
on the Companion object and in the case of multiple constructors (as e.g. Credential), they should be suspend createFoo()
and suspend createBar()
functions on the Companion object, I think. It would be good if that were made more consistent...
private const val METADATA_PREFIX = "@" | ||
|
||
/** | ||
* Creates an instance of AndroidKeystoreSecureArea. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ITYM CloudSecureArea here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -444,6 +447,8 @@ class DeviceRetrievalHelper internal constructor( | |||
* The [Listener.onError] callback can be called at any time - for | |||
* example - if the remote verifier disconnects without using session termination or if the | |||
* underlying transport encounters an unrecoverable error. | |||
* | |||
* TODO: consider redoing this API through flows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, DeviceRetrievalHelper
is now replaced by PresentmentModel
and MdocTransport
which both use flows. The idea is to deprecate all of identity-android.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, removed that TODO
@@ -454,7 +459,7 @@ class DeviceRetrievalHelper internal constructor( | |||
* | |||
* @param eReaderKey the ephemeral reader key. | |||
*/ | |||
fun onEReaderKeyReceived(eReaderKey: EcPublicKey) | |||
suspend fun onEReaderKeyReceived(eReaderKey: EcPublicKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced we need to make these suspend
. What's the rationale?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is going away, I'll put it back the way it was. I'll just need to add runBlocking in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's going to be deprecated and will be moved to multipaz-legacy library with the rest of identity-android and identity-android-legacy. We'll keep using it in wallet
, other downstreams may keep using it, so it needs to keep working.
I think runBlocking before invoking this will be fine. The listener implementations are not supposed to be doing any blocking I/O anyway and I don't think any of them do.
*/ | ||
suspend fun create(context: Context, storage: Storage): AndroidKeystoreSecureArea { | ||
return AndroidKeystoreSecureArea(context, storage.getTable(tableSpec)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A separate create() function is needed b/c constructors cannot be suspend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, storage.getTable(tableSpec)
is asynchronous
@@ -102,13 +105,15 @@ import java.io.ByteArrayInputStream | |||
* what the device supports. | |||
* | |||
* This implementation works only on Android and requires API level 24 or later. | |||
* | |||
* Use [AndroidKeystoreSecureArea.create] to create an instance of this class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make the constructor private or protected? Seems like that would force users to use create()...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is an oversight.
*/ | ||
fun createKey(alias: String, createKeySettings: CreateKeySettings) | ||
suspend fun createKey(alias: String?, createKeySettings: CreateKeySettings): String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we want this to return KeyInfo
in the future.... maybe add alias
to KeyInfo
and return KeyInfo
now? I think that's only a few lines of code...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
* requested URL and configure the instance as needed. Key to the map is Secure Area | ||
* identifier prefix of the Secure Area identifier up until the '?' character. | ||
* @param factoryFunc a function to create a new Secure Area with the requested identifier. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment needs both formatting changes (two empty lines, "can" should start with a capital) and also references now non-existant factoryFunc
builder.block() | ||
builder.build() | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a non-standard builder pattern... do we even need a builder-ish thing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty standard Kotlin DSL... And what it does is trickier than it seems:
// This builds SecureAreaRepository *synchronously*, can be called in static initializers
val r = SecureAreaRepository.build {
// *asynchronous code* that does actual building
add(...)
addImplementation(...)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, just checking, I'm pretty rusty when it comes to DSLs in Kotlin. Fine with me!
* @param docType the docType of the credential | ||
* | ||
* [SecureAreaBoundCredential.generateKey] must be called before using this object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you have this requirement b/c constructors can't be suspend. How about making the constructor private/protected and have a static suspend create() instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the same issue as in SecureAreaBoundCredential. I agree that we want something like this, but I think these APIs probably should be designed for concurrency/locking from grounds up.
@@ -38,38 +38,46 @@ open class SecureAreaBoundCredential : Credential { | |||
/** | |||
* Constructs a new [SecureAreaBoundCredential]. | |||
* | |||
* [generateKey] providing [CreateKeySettings] must be called before using this object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as with MdocCredential, make the constructor private and have a static suspend create() that plays the role of a suspend constructor.
46a0dbf
to
793d828
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, LGTM to merge now and we can work on addressing concurrency/locking and API for DocumentStore, Document, and Credential in separate PRs. Cc @kdeus
I've done a few spot checks, not a thorough review. |
Signed-off-by: Peter Sorotokin <[email protected]>
793d828
to
2ec71e5
Compare
Moves to new Storage API and propagates asynchronous ("suspend") function signatures through the existing code.
We will need to define concurrency model for DocumentStore/Document/Credential APIs in future PRs.
Also adjusts unit tests to use new APIs