stac_extensions/
authentication.rs

1//! Provides a standard set of fields to describe authentication and
2//! authorization schemes, flows, and scopes required to access
3//! [Assets](stac::Asset) and [Links](stac::Link) that align with the [OpenAPI
4//! security spec](https://swagger.io/docs/specification/authentication/).
5
6use crate::Extension;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// The authentication extension fields.
12#[derive(Debug, Serialize, Deserialize)]
13pub struct Authentication {
14    /// A property that contains all of the [scheme definitions](Scheme) used by
15    /// [Assets](stac::Asset) and [Links](stac::Link) in the STAC [Item](crate::Item) or [Collection](crate::Collection).
16    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
17    pub schemes: HashMap<String, Scheme>,
18
19    /// A property that specifies which schemes may be used to access an [Asset](stac::Asset)
20    /// or [Link](stac::Link).
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub refs: Vec<String>,
23}
24
25/// The Authentication Scheme extends the [OpenAPI security
26/// spec](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object)
27/// for support of OAuth2.0, API Key, and OpenID Connect authentication.
28#[derive(Debug, Serialize, Deserialize)]
29pub struct Scheme {
30    /// The authentication scheme type used to access the data (`http` | `s3` |
31    /// `signedUrl` | `oauth2` | `apiKey` | `openIdConnect` | a custom scheme type ).
32    pub r#type: String,
33
34    /// Additional instructions for authentication.
35    ///
36    /// [CommonMark 0.29](https://commonmark.org/) syntax MAY be used for rich text representation.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub description: Option<String>,
39
40    /// The name of the header, query, or cookie parameter to be used.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub name: Option<String>,
43
44    /// The location of the API key (`query` | `header` | `cookie`).
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub r#in: Option<In>,
47
48    /// The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235.
49    ///
50    /// The values used SHOULD be registered in the IANA Authentication Scheme registry.
51    /// (`basic` | `bearer` | `digest` | `dpop` | `hoba` | `mutual` |
52    /// `negotiate` | `oauth` (1.0) | `privatetoken` | `scram-sha-1` |
53    /// `scram-sha-256` | `vapid`)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub scheme: Option<String>,
56
57    /// Scenarios an API client performs to get an access token from the authorization server.
58    ///
59    /// For oauth2 the following keys are pre-defined for the corresponding
60    /// OAuth flows: `authorizationCode` | `implicit` | `password` |
61    /// `clientCredentials`.  The OAuth2 Flow Object applies for oauth2, the
62    /// Signed URL Object applies to signedUrl.
63    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
64    pub flows: HashMap<String, Flow>,
65
66    /// OpenID Connect URL to discover OpenID configuration values.
67    ///
68    /// This MUST be in the form of a URL.
69    #[serde(skip_serializing_if = "Option::is_none", rename = "openIdConnectUrl")]
70    pub open_id_connect_url: Option<String>,
71}
72
73/// The OAuth2 Flow Object applies for oauth2, the Signed URL Object applies to signedUrl.
74#[derive(Debug, Serialize, Deserialize)]
75#[serde(untagged)]
76pub enum Flow {
77    /// Based on the [OpenAPI OAuth Flow
78    /// Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object).
79    ///
80    /// Allows configuration of the supported OAuth Flows.
81    OAuth2 {
82        /// The authorization URL to be used for this flow.
83        ///
84        /// This MUST be in the form of a URL.
85        #[serde(skip_serializing_if = "Option::is_none", rename = "authorizationUrl")]
86        authorization_url: Option<String>,
87
88        /// The token URL to be used for this flow.
89        ///
90        /// This MUST be in the form of a URL.
91        #[serde(skip_serializing_if = "Option::is_none", rename = "tokenUrl")]
92        token_url: Option<String>,
93
94        /// The available scopes for the authentication scheme.
95        ///
96        /// A map between the scope name and a short description for it. The map MAY be empty.
97        scopes: HashMap<String, String>,
98
99        /// The URL to be used for obtaining refresh tokens.
100        ///
101        /// This MUST be in the form of a URL.
102        #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
103        refresh_url: Option<String>,
104    },
105
106    /// A signed url flow.
107    SignedUrl {
108        /// The method to be used for requests.
109        method: String,
110
111        /// The signed URL API endpoint to be used for this flow.
112        ///
113        /// If not inferred from the client environment, this must be defined in the authentication flow.
114        #[serde(skip_serializing_if = "Option::is_none", rename = "authorizationApi")]
115        authorization_api: Option<String>,
116
117        /// Parameter definition for requests to the authorizationApi
118        #[serde(skip_serializing_if = "HashMap::is_empty")]
119        parameters: HashMap<String, Parameter>,
120
121        /// Key name for the signed URL field in an authorizationApi response
122        #[serde(skip_serializing_if = "Option::is_none", rename = "responseField")]
123        response_field: Option<String>,
124    },
125}
126
127/// Definition for a request parameter.
128#[derive(Debug, Serialize, Deserialize)]
129pub struct Parameter {
130    /// The location of the parameter (`query` | `header` | `body`).
131    pub r#in: String,
132
133    /// Setting for optional or required parameter.
134    pub required: bool,
135
136    /// Plain language description of the parameter
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub description: Option<String>,
139
140    /// Schema object following the [JSON Schema draft-07](Schema object following the JSON Schema draft-07).
141    pub schema: HashMap<String, Value>,
142}
143
144/// Query, header, or cookie.
145#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
146pub enum In {
147    /// In the GET query string.
148    #[serde(rename = "query")]
149    Query,
150
151    /// In the headers.
152    #[default]
153    #[serde(rename = "header")]
154    Header,
155
156    /// In the cookie.
157    #[serde(rename = "cookie")]
158    Cookie,
159}
160
161impl Extension for Authentication {
162    const IDENTIFIER: &'static str =
163        "https://stac-extensions.github.io/authentication/v1.1.0/schema.json";
164    const PREFIX: &'static str = "auth";
165}
166
167#[cfg(test)]
168mod tests {
169    use super::{Authentication, In, Scheme};
170    use crate::{Collection, Extensions, Item};
171    use serde_json::json;
172
173    #[test]
174    fn collection() {
175        let collection: Collection = stac::read("data/auth/collection.json").unwrap();
176        let authentication: Authentication = collection.extension().unwrap();
177        let oauth = authentication.schemes.get("oauth").unwrap();
178        let _ = oauth.flows.get("authorizationCode").unwrap();
179    }
180
181    #[test]
182    fn item() {
183        let collection: Item = stac::read("data/auth/item.json").unwrap();
184        let authentication: Authentication = collection.extension().unwrap();
185        let _ = authentication.schemes.get("none").unwrap();
186    }
187
188    #[test]
189    fn api_key() {
190        let scheme: Scheme = serde_json::from_value(json!({
191          "type": "apiKey",
192          "in": "query",
193          "name": "API_KEY"
194        }))
195        .unwrap();
196        assert_eq!(scheme.r#in.unwrap(), In::Query);
197    }
198}