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

fix(ci): Fix custom_time feature ci tests #1400

Closed
wants to merge 12 commits into from
25 changes: 14 additions & 11 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches:
- main
pull_request:
types: [ opened, synchronize, reopened, ready_for_review ]
types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
- 'epic/**'
Expand Down Expand Up @@ -62,12 +62,12 @@ jobs:

build-and-test:
runs-on: ${{ matrix.os }}
needs: [ check-for-run-condition, check-for-modification ]
needs: [check-for-run-condition, check-for-modification]
if: ${{ needs.check-for-run-condition.outputs.should-run == 'true' && needs.check-for-modification.outputs.core-modified == 'true' }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
os: [ubuntu-latest, macos-latest, windows-latest]
include:
- os: ubuntu-latest
sccache-path: /home/runner/.cache/sccache
Expand Down Expand Up @@ -127,14 +127,17 @@ jobs:

# Build the library, tests, and examples without running them to avoid recompilation in the run tests step
- name: Build with all features
run: cargo build --workspace --tests --examples --all-features --release
run: cargo build --workspace --tests --examples --release

- name: Start iota sandbox
if: matrix.os == 'ubuntu-latest'
uses: './.github/actions/iota-sandbox/setup'

- name: Run tests
run: cargo test --workspace --all-features --release
- name: Run tests excluding `custom_time` feature
run: cargo test --workspace --release

- name: Run tests with `custom_time` feature
run: cargo test --test custom_time --features="custom_time"

- name: Run Rust examples
# run examples only on ubuntu for now
Expand All @@ -157,7 +160,7 @@ jobs:
- name: Tear down iota sandbox
if: matrix.os == 'ubuntu-latest' && always()
uses: './.github/actions/iota-sandbox/tear-down'

- name: Stop sccache
uses: './.github/actions/rust/sccache/stop-sccache'
with:
Expand All @@ -178,7 +181,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
os: [ubuntu-latest]
include:
- os: ubuntu-latest

Expand Down Expand Up @@ -218,7 +221,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
os: [ubuntu-latest]
include:
- os: ubuntu-latest

Expand All @@ -229,7 +232,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: 16.x

- name: Install JS dependencies
run: npm ci
working-directory: bindings/wasm
Expand Down Expand Up @@ -266,7 +269,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
os: [ubuntu-latest]
include:
- os: ubuntu-latest

Expand Down
11 changes: 9 additions & 2 deletions identity_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ rust-version.workspace = true
description = "The core traits and types for the identity-rs library."

[dependencies]
iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "random", "sha", "x25519", "std"] }
multibase = { version = "0.9", default-features = false, features = ["std"] }
serde = { workspace = true, features = ["std"] }
serde_json = { workspace = true, features = ["std"] }
Expand All @@ -22,7 +21,7 @@ time = { version = "0.3.23", default-features = false, features = ["std", "serde
url = { version = "2.4", default-features = false, features = ["serde"] }
zeroize = { version = "1.6", default-features = false }

[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies]
[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "custom_time")))'.dependencies]
js-sys = { version = "0.3.55", default-features = false }

[dev-dependencies]
Expand All @@ -38,3 +37,11 @@ rustdoc-args = ["--cfg", "docsrs"]

[lints]
workspace = true

[features]
# Enables a macro to provide a custom time (Timestamp::now_utc) implementation, see src/custom_time.rs
custom_time = []

[[test]]
name = "custom_time"
required-features = ["custom_time"]
2 changes: 1 addition & 1 deletion identity_core/src/common/ordered_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ mod tests {
/// Produces a strategy for generating an ordered set together with two values according to the following algorithm:
/// 1. Call `f` to get a pair of sets (x,y).
/// 2. Toss a coin to decide whether to pick an element from x at random, or from y (if the chosen set is empty
/// Default is called). 3. Repeat step 2 and let the two outcomes be denoted a and b.
/// Default is called). 3. Repeat step 2 and let the two outcomes be denoted a and b.
/// 4. Toss a coin to decide whether to swap the keys of a and b.
/// 5. return (x,a,b)
fn set_with_values<F, T, U>(f: F) -> impl Strategy<Value = (OrderedSet<T>, T, T)>
Expand Down
16 changes: 14 additions & 2 deletions identity_core/src/common/timestamp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ impl Timestamp {
/// fractional seconds truncated.
///
/// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production).
#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
#[cfg(all(
not(all(target_arch = "wasm32", not(target_os = "wasi"))),
not(feature = "custom_time")
))]
pub fn now_utc() -> Self {
Self(truncate_fractional_seconds(OffsetDateTime::now_utc()))
}
Expand All @@ -51,14 +54,23 @@ impl Timestamp {
/// fractional seconds truncated.
///
/// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production).
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "custom_time")))]
pub fn now_utc() -> Self {
let milliseconds_since_unix_epoch: i64 = js_sys::Date::now() as i64;
let seconds: i64 = milliseconds_since_unix_epoch / 1000;
// expect is okay, we assume the current time is between 0AD and 9999AD
Self::from_unix(seconds).expect("Timestamp failed to convert system datetime")
}

/// Creates a new `Timestamp` with the current date and time, normalized to UTC+00:00 with
/// fractional seconds truncated.
///
/// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production).
#[cfg(feature = "custom_time")]
pub fn now_utc() -> Self {
crate::custom_time::now_utc_custom()
}

/// Returns the `Timestamp` as an [RFC 3339](https://tools.ietf.org/html/rfc3339) `String`.
pub fn to_rfc3339(&self) -> String {
// expect is okay, constructors ensure RFC 3339 compatible timestamps.
Expand Down
88 changes: 88 additions & 0 deletions identity_core/src/custom_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! An implementation of `now_utc` which calls out to an externally defined function.
use crate::common::Timestamp;

/// Register a function to be invoked by `identity_core` in order to get a [Timestamp] representing
/// "now".
///
/// ## Writing a custom `now_utc` implementation
///
/// The function to register must have the same signature as
/// [`Timestamp::now_utc`](Timestamp::now_utc). The function can be defined
/// wherever you want, either in root crate or a dependent crate.
///
/// For example, if we wanted a `static_now_utc` crate containing an
/// implementation that always returns the same timestamp, we would first depend on `identity_core`
/// (for the [`Timestamp`] type) in `static_now_utc/Cargo.toml`:
/// ```toml
/// [dependencies]
/// identity_core = "1"
/// ```
/// Note that the crate containing this function does **not** need to enable the
/// `"custom_time"` Cargo feature.
///
/// Next, in `static_now_utc/src/lib.rs`, we define our function:
/// ```rust
/// use identity_core::common::Timestamp;
///
/// // Some fixed timestamp
/// const MY_FIXED_TIMESTAMP: i64 = 1724402964;
/// pub fn static_now_utc() -> Timestamp {
/// Timestamp::from_unix(MY_FIXED_TIMESTAMP).unwrap()
/// }
/// ```
///
/// ## Registering a custom `now_utc` implementation
///
/// Functions can only be registered in the root binary crate. Attempting to
/// register a function in a non-root crate will result in a linker error.
/// This is similar to
/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or
/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html),
/// where helper crates define handlers/allocators but only the binary crate
/// actually _uses_ the functionality.
///
/// To register the function, we first depend on `static_now_utc` _and_
/// `identity_core` in `Cargo.toml`:
/// ```toml
/// [dependencies]
/// static_now_utc = "0.1"
/// identity_core = { version = "1", features = ["custom_time"] }
/// ```
///
/// Then, we register the function in `src/main.rs`:
/// ```rust
/// # mod static_now_utc { pub fn static_now_utc() -> Timestamp { unimplemented!() } }
///
/// use identity_core::register_custom_now_utc;
/// use static_now_utc::static_now_utc;
///
/// register_custom_now_utc!(static_now_utc);
/// ```
///
/// Now any user of `now_utc` (direct or indirect) on this target will use the
/// registered function.
#[macro_export]
macro_rules! register_custom_now_utc {
($path:path) => {
const __GET_TIME_INTERNAL: () = {
// We use Rust ABI to be safe against potential panics in the passed function.
#[no_mangle]
unsafe fn __now_utc_custom() -> Timestamp {
// Make sure the passed function has the type of `now_utc_custom`
type F = fn() -> Timestamp;
let f: F = $path;
f()
}
};
};
}

pub(crate) fn now_utc_custom() -> Timestamp {
extern "Rust" {
fn __now_utc_custom() -> Timestamp;
}
unsafe { __now_utc_custom() }
}
7 changes: 6 additions & 1 deletion identity_core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#![forbid(unsafe_code)]
#![doc = include_str!("./../README.md")]
#![allow(clippy::upper_case_acronyms)]
#![warn(
Expand All @@ -19,9 +18,15 @@
#[doc(inline)]
pub use serde_json::json;

#[forbid(unsafe_code)]
pub mod common;
#[forbid(unsafe_code)]
pub mod convert;
#[forbid(unsafe_code)]
pub mod error;

#[cfg(feature = "custom_time")]
pub mod custom_time;

pub use self::error::Error;
pub use self::error::Result;
18 changes: 18 additions & 0 deletions identity_core/tests/custom_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_core::common::Timestamp;
use identity_core::register_custom_now_utc;

const STATIC_TIME: i64 = 1724402964; // 2024-08-23T11:33:30+00:00
pub fn static_now_utc() -> Timestamp {
Timestamp::from_unix(STATIC_TIME).unwrap()
}

register_custom_now_utc!(static_now_utc);

#[test]
fn should_use_registered_static_time() {
let timestamp = Timestamp::now_utc();
assert_eq!(timestamp.to_unix(), STATIC_TIME)
}
2 changes: 1 addition & 1 deletion identity_credential/src/credential/jwt_serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::Result;
/// This type is opinionated in the following ways:
/// 1. Serialization tries to duplicate as little as possible between the required registered claims and the `vc` entry.
/// 2. Only allows serializing/deserializing claims "exp, iss, nbf &/or iat, jti, sub and vc". Other custom properties
/// must be set in the `vc` entry.
/// must be set in the `vc` entry.
#[derive(Serialize, Deserialize)]
pub(crate) struct CredentialJwtClaims<'credential, T = Object>
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ impl<V: JwsVerifier> JwtDomainLinkageValidator<V> {
/// Validates the linkage between a domain and a DID.
/// [`DomainLinkageConfiguration`] is validated according to [DID Configuration Resource Verification](https://identity.foundation/.well-known/resources/did-configuration/#did-configuration-resource-verification).
///
/// * `issuer`: DID Document of the linked DID. Issuer of the Domain Linkage Credential included
/// in the Domain Linkage Configuration.
/// * `issuer`: DID Document of the linked DID. Issuer of the Domain Linkage Credential included in the Domain Linkage
/// Configuration.
/// * `configuration`: Domain Linkage Configuration fetched from the domain at "/.well-known/did-configuration.json".
/// * `domain`: domain from which the Domain Linkage Configuration has been fetched.
/// * `validation_options`: Further validation options to be applied on the Domain Linkage Credential.
///
/// # Note:
/// - Only the [JSON Web Token Proof Format](https://identity.foundation/.well-known/resources/did-configuration/#json-web-token-proof-format)
/// is supported.
/// is supported.
/// - Only the Credential issued by `issuer` is verified.
///
/// # Errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl StatusList2021Credential {
///
/// ## Note:
/// - A revoked credential cannot ever be unrevoked and will lead to a
/// [`StatusList2021CredentialError::UnreversibleRevocation`].
/// [`StatusList2021CredentialError::UnreversibleRevocation`].
/// - Trying to set `revoked_or_suspended` to `false` for an already valid credential will have no impact.
pub fn set_credential_status(
&mut self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ where
/// # Warning
///
/// * This method does NOT validate the constituent credentials and therefore also not the relationship between the
/// credentials' subjects and the presentation holder. This can be done with
/// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions).
/// credentials' subjects and the presentation holder. This can be done with
/// [`JwtCredentialValidationOptions`](crate::validator::JwtCredentialValidationOptions).
/// * The lack of an error returned from this method is in of itself not enough to conclude that the presentation can
/// be trusted. This section contains more information on additional checks that should be carried out before and
/// after calling this method.
/// be trusted. This section contains more information on additional checks that should be carried out before and
/// after calling this method.
///
/// ## The state of the supplied DID Documents.
///
Expand Down
6 changes: 3 additions & 3 deletions identity_credential/src/validator/sd_jwt/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ impl<V: JwsVerifier> SdJwtCredentialValidator<V> {
///
/// # Warning
/// * The key binding JWT is not validated. If needed, it must be validated separately using
/// `SdJwtValidator::validate_key_binding_jwt`.
/// `SdJwtValidator::validate_key_binding_jwt`.
/// * The lack of an error returned from this method is in of itself not enough to conclude that the credential can be
/// trusted. This section contains more information on additional checks that should be carried out before and after
/// calling this method.
/// trusted. This section contains more information on additional checks that should be carried out before and after
/// calling this method.
///
/// ## The state of the issuer's DID Document
/// The caller must ensure that `issuer` represents an up-to-date DID Document.
Expand Down
4 changes: 2 additions & 2 deletions identity_document/src/document/core_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,8 +938,8 @@ impl CoreDocument {
/// Regardless of which options are passed the following conditions must be met in order for a verification attempt to
/// take place.
/// - The JWS must be encoded according to the JWS compact serialization.
/// - The `kid` value in the protected header must be an identifier of a verification method in this DID document,
/// or set explicitly in the `options`.
/// - The `kid` value in the protected header must be an identifier of a verification method in this DID document, or
/// set explicitly in the `options`.
//
// NOTE: This is tested in `identity_storage` and `identity_credential`.
pub fn verify_jws<'jws, T: JwsVerifier>(
Expand Down
3 changes: 1 addition & 2 deletions identity_jose/src/jwu/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ pub(crate) fn validate_jws_headers(protected: Option<&JwsHeader>, unprotected: O
/// Validates that the "crit" parameter satisfies the following requirements:
/// 1. It is integrity protected.
/// 2. It is not encoded as an empty list.
/// 3. It does not contain any header parameters defined by the
/// JOSE JWS/JWA specifications.
/// 3. It does not contain any header parameters defined by the JOSE JWS/JWA specifications.
/// 4. It's values are contained in the given `permitted` array.
/// 5. All values in "crit" are present in at least one of the `protected` or `unprotected` headers.
///
Expand Down
Loading
Loading