1use crate::{sqlite::DatabaseConnection, DbError, NewOperation, OperationId, PendingValue};
4use coven_crypto::SecretBytes;
5
6pub(crate) const REQUEST_QUEUED_STEP: u32 = 0;
10pub const REQUEST_FIXED_STEP: u32 = 1;
13pub(crate) const REQUEST_FAILED_STEP: u32 = 2;
14
15pub struct ProviderAccessChange {
17 pub account: String,
19 pub intended: bool,
21 pub initially_granted: bool,
23 pub needs_invitation: bool,
25 pub operation: NewOperation,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct ProviderAccessRequest {
32 pub operation: OperationId,
34 pub target: Option<bool>,
36}
37
38#[derive(Debug)]
40pub struct ProviderAccess {
41 pub intended: bool,
43 pub granted: Option<bool>,
45 pub invitation: Option<SecretBytes>,
47 pub request: Option<ProviderAccessRequest>,
49}
50
51pub(crate) fn read(
52 db: &DatabaseConnection,
53 account: &str,
54) -> Result<Option<ProviderAccess>, DbError> {
55 db.query("SELECT intended,granted,invitation,operation,target FROM _coven_provider_access WHERE account=?1", [account], |row| {
56 let operation: Option<i64> = row.get(3)?;
57 let target: Option<bool> = row.get(4)?;
58 if operation.is_none() && target.is_some() { return Err(crate::write_encoding::damaged(4, rusqlite::types::Type::Integer)); }
59 Ok(ProviderAccess { intended: row.get(0)?, granted: row.get(1)?, invitation: row.get::<_,Option<Vec<u8>>>(2)?.map(SecretBytes::new),
60 request: operation.map(|id| ProviderAccessRequest { operation: OperationId(id), target }) })
61 }).map(|mut rows| rows.pop())
62}
63
64pub(crate) fn finish(
65 db: &DatabaseConnection,
66 account: &str,
67 id: OperationId,
68) -> Result<(), DbError> {
69 db.internal_execute("UPDATE _coven_provider_access SET operation=NULL,target=NULL WHERE account=?1 AND operation=?2", (account,id.0))?;
70 crate::operation::finish(db, id)?;
71 Ok(())
72}
73
74pub(crate) fn requeue(
77 db: &DatabaseConnection,
78 account: &str,
79 id: OperationId,
80) -> Result<(), DbError> {
81 crate::operation::set_step(db, id, REQUEST_QUEUED_STEP)?;
82 crate::retry::clear(db, crate::RetrySubject::Operation(id))?;
83 db.internal_execute(
84 "UPDATE _coven_provider_access SET target=NULL WHERE account=?1 AND target IS NOT NULL",
85 [account],
86 )?;
87 Ok(())
88}
89
90pub(crate) fn fix_target(db: &DatabaseConnection, account: &str) -> Result<(), DbError> {
95 db.internal_execute("UPDATE _coven_provider_access SET granted=NULL,invitation=NULL,target=intended WHERE account=?1", [account])?;
96 Ok(())
97}
98
99pub(crate) fn confirm(
102 db: &DatabaseConnection,
103 account: &str,
104 target: bool,
105 invitation: Option<&SecretBytes>,
106) -> Result<(), DbError> {
107 db.internal_execute(
108 "UPDATE _coven_provider_access SET granted=?1,invitation=?2,target=NULL WHERE account=?3",
109 (target, invitation.map(SecretBytes::as_bytes), account),
110 )?;
111 Ok(())
112}
113
114pub(crate) fn adopt<P: PendingValue>(
116 db: &DatabaseConnection,
117 changes: &[ProviderAccessChange],
118) -> Result<(), DbError> {
119 for change in changes {
120 let before = match read(db, &change.account)? {
121 Some(before) => before,
122 None => {
123 db.internal_execute(
124 "INSERT INTO _coven_provider_access(account,intended,granted) VALUES(?1,?2,?3)",
125 (&change.account, change.intended, change.initially_granted),
126 )?;
127 ProviderAccess {
128 intended: change.intended,
129 granted: Some(change.initially_granted),
130 invitation: None,
131 request: None,
132 }
133 }
134 };
135 db.internal_execute(
136 "UPDATE _coven_provider_access SET intended=?1 WHERE account=?2 AND intended!=?1",
137 (change.intended, &change.account),
138 )?;
139 if let Some(request) = &before.request {
140 let record =
141 crate::operation::find(db, request.operation)?.ok_or(DbError::DamagedDatabase)?;
142 if record.last_step == REQUEST_FIXED_STEP
145 || (record.last_step == REQUEST_FAILED_STEP && before.intended == change.intended)
146 {
147 continue;
148 }
149 }
150 let needed = before.granted != Some(change.intended)
151 || (change.intended && change.needs_invitation && before.invitation.is_none());
152 if !needed {
153 if let Some(request) = before.request {
154 finish(db, &change.account, request.operation)?;
155 }
156 continue;
157 }
158 let id = match before.request {
159 Some(request) => {
160 requeue(db, &change.account, request.operation)?;
161 request.operation
162 }
163 None => {
164 let id = crate::operation::insert(db, &change.operation)?;
165 db.internal_execute(
166 "UPDATE _coven_provider_access SET operation=?1 WHERE account=?2",
167 (id.0, &change.account),
168 )?;
169 id
170 }
171 };
172 crate::pending::save(db, &P::provider_access(id, change.account.clone(), change.intended))?;
173 }
174 Ok(())
175}
176
177pub(crate) fn retake<P: PendingValue>(
186 db: &DatabaseConnection,
187 changes: &[ProviderAccessChange],
188) -> Result<(), DbError> {
189 for change in changes {
190 if let Some(request) = read(db, &change.account)?.and_then(|state| state.request) {
191 let record =
192 crate::operation::find(db, request.operation)?.ok_or(DbError::DamagedDatabase)?;
193 if record.last_step == REQUEST_FIXED_STEP {
194 return Err(DbError::ProviderRequestRunning);
195 }
196 if record.last_step == REQUEST_FAILED_STEP {
197 requeue(db, &change.account, request.operation)?;
200 }
201 }
202 db.internal_execute(
203 "INSERT INTO _coven_provider_access(account,intended) VALUES(?1,?2) \
204 ON CONFLICT(account) DO NOTHING",
205 (&change.account, change.intended),
206 )?;
207 db.internal_execute(
208 "UPDATE _coven_provider_access SET granted=NULL,invitation=NULL WHERE account=?1",
209 [&change.account],
210 )?;
211 }
212 adopt::<P>(db, changes)
213}
214
215#[cfg(test)]
216#[path = "provider_access_tests.rs"]
217pub(crate) mod tests;