Add authentication back into the operation states
parent: tbd commit: 97a26fd
Showing 9 changed files with 167 insertions and 35 deletions
giterated-daemon/src/backend/git.rs
@@ -6,12 +6,12 @@ use giterated_models::instance::{Instance, RepositoryCreateRequest}; | ||
6 | 6 | |
7 | 7 | use giterated_models::repository::{ |
8 | 8 | AccessList, Commit, DefaultBranch, Description, IssueLabel, LatestCommit, Repository, |
9 | RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffFile, RepositoryDiffFileChunk, | |
10 | RepositoryDiffFileInfo, RepositoryDiffFileStatus, RepositoryDiffPatchRequest, | |
11 | RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, | |
12 | RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest, | |
13 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositoryObjectType, | |
14 | RepositoryTreeEntry, RepositoryVisibility, Visibility, RepositoryChunkLine, RepositoryFileFromPathRequest, | |
9 | RepositoryChunkLine, RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffFile, | |
10 | RepositoryDiffFileChunk, RepositoryDiffFileInfo, RepositoryDiffFileStatus, | |
11 | RepositoryDiffPatchRequest, RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, | |
12 | RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue, | |
13 | RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
14 | RepositoryObjectType, RepositoryTreeEntry, RepositoryVisibility, Visibility, | |
15 | 15 | }; |
16 | 16 | use giterated_models::settings::{AnySetting, Setting}; |
17 | 17 | use giterated_models::user::{User, UserParseError}; |
@@ -50,7 +50,10 @@ impl GitRepository { | ||
50 | 50 | user: Option<&User>, |
51 | 51 | settings: &Arc<Mutex<dyn MetadataBackend + Send>>, |
52 | 52 | ) -> bool { |
53 | info!("Can user {:?} view repository {}/{}?", user, self.owner_user, self.name); | |
53 | info!( | |
54 | "Can user {:?} view repository {}/{}?", | |
55 | user, self.owner_user, self.name | |
56 | ); | |
54 | 57 | if matches!(self.visibility, RepositoryVisibility::Public) { |
55 | 58 | return true; |
56 | 59 | } |
@@ -240,7 +243,10 @@ impl GitBackend { | ||
240 | 243 | name: &str, |
241 | 244 | requester: Option<&User>, |
242 | 245 | ) -> Result<git2::Repository, GitBackendError> { |
243 | info!("Checking permissions for user {:?} on {}/{}", requester, owner, name); | |
246 | info!( | |
247 | "Checking permissions for user {:?} on {}/{}", | |
248 | requester, owner, name | |
249 | ); | |
244 | 250 | |
245 | 251 | let repository = match self |
246 | 252 | .find_by_owner_user_name( |
@@ -661,7 +667,7 @@ impl RepositoryBackend for GitBackend { | ||
661 | 667 | return Err(Box::new(GitBackendError::RefNotFound( |
662 | 668 | request.rev.clone().unwrap(), |
663 | 669 | )) |
664 | .into()) | |
670 | .into()); | |
665 | 671 | } |
666 | 672 | } |
667 | 673 | Some(rev_name) => { |
@@ -717,7 +723,9 @@ impl RepositoryBackend for GitBackend { | ||
717 | 723 | binary: blob.is_binary(), |
718 | 724 | size: blob.size(), |
719 | 725 | }, |
720 | Err(_) => return Err(Box::new(GitBackendError::BlobNotFound(entry.id().to_string())).into()), | |
726 | Err(_) => { | |
727 | return Err(Box::new(GitBackendError::BlobNotFound(entry.id().to_string())).into()) | |
728 | } | |
721 | 729 | }; |
722 | 730 | |
723 | 731 | Ok(file) |
@@ -813,7 +821,7 @@ impl RepositoryBackend for GitBackend { | ||
813 | 821 | change_type: line.origin_value().into(), |
814 | 822 | content: line_utf8, |
815 | 823 | old_line_num: line.old_lineno(), |
816 | new_line_num: line.new_lineno() | |
824 | new_line_num: line.new_lineno(), | |
817 | 825 | }); |
818 | 826 | } |
819 | 827 |
giterated-daemon/src/backend/mod.rs
@@ -18,8 +18,9 @@ use giterated_models::instance::{ | ||
18 | 18 | use giterated_models::repository::{ |
19 | 19 | Commit, IssueLabel, Repository, RepositoryCommitBeforeRequest, RepositoryDiff, |
20 | 20 | RepositoryDiffPatchRequest, RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, |
21 | RepositoryFileInspectRequest, RepositoryIssue, RepositoryIssueLabelsRequest, | |
22 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, RepositorySummary, RepositoryTreeEntry, RepositoryFileFromPathRequest, | |
21 | RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryIssue, | |
22 | RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
23 | RepositorySummary, RepositoryTreeEntry, | |
23 | 24 | }; |
24 | 25 | use giterated_models::settings::AnySetting; |
25 | 26 | use giterated_models::user::User; |
giterated-daemon/src/connection/wrapper.rs
@@ -1,12 +1,17 @@ | ||
1 | 1 | use std::{ |
2 | 2 | net::SocketAddr, |
3 | ops::Deref, | |
3 | 4 | sync::{atomic::AtomicBool, Arc}, |
4 | 5 | }; |
5 | 6 | |
6 | 7 | use anyhow::Error; |
7 | 8 | use futures_util::{SinkExt, StreamExt}; |
8 | 9 | |
9 | use giterated_models::{error::OperationError, instance::Instance}; | |
10 | use giterated_models::{ | |
11 | authenticated::{AuthenticationSource, UserTokenMetadata}, | |
12 | error::OperationError, | |
13 | instance::Instance, | |
14 | }; | |
10 | 15 | |
11 | 16 | use giterated_models::object_backend::ObjectBackend; |
12 | 17 | |
@@ -14,7 +19,17 @@ use giterated_models::{ | ||
14 | 19 | authenticated::AuthenticatedPayload, message::GiteratedMessage, object::AnyObject, |
15 | 20 | operation::AnyOperation, |
16 | 21 | }; |
17 | use giterated_stack::{handler::GiteratedBackend, StackOperationState}; | |
22 | use giterated_stack::{ | |
23 | handler::GiteratedBackend, AuthenticatedInstance, AuthenticatedUser, StackOperationState, | |
24 | }; | |
25 | use jsonwebtoken::{DecodingKey, TokenData, Validation}; | |
26 | use rsa::{ | |
27 | pkcs1::DecodeRsaPublicKey, | |
28 | pss::{Signature, VerifyingKey}, | |
29 | sha2::Sha256, | |
30 | signature::Verifier, | |
31 | RsaPublicKey, | |
32 | }; | |
18 | 33 | use serde::Serialize; |
19 | 34 | |
20 | 35 | use tokio::{net::TcpStream, sync::Mutex}; |
@@ -43,7 +58,7 @@ pub async fn connection_wrapper( | ||
43 | 58 | instance_connections: Arc<Mutex<InstanceConnections>>, |
44 | 59 | config: Table, |
45 | 60 | backend: GiteratedBackend<DatabaseBackend>, |
46 | operation_state: StackOperationState, | |
61 | mut operation_state: StackOperationState, | |
47 | 62 | ) { |
48 | 63 | let connection_state = ConnectionState { |
49 | 64 | socket: Arc::new(Mutex::new(socket)), |
@@ -61,6 +76,7 @@ pub async fn connection_wrapper( | ||
61 | 76 | }; |
62 | 77 | |
63 | 78 | let _handshaked = false; |
79 | let mut key_cache = PublicKeyCache::default(); | |
64 | 80 | |
65 | 81 | loop { |
66 | 82 | let mut socket = connection_state.socket.lock().await; |
@@ -83,8 +99,76 @@ pub async fn connection_wrapper( | ||
83 | 99 | |
84 | 100 | let message: AuthenticatedPayload = bincode::deserialize(&payload).unwrap(); |
85 | 101 | |
102 | // Get authentication | |
103 | let instance = { | |
104 | let mut verified_instance: Option<AuthenticatedInstance> = None; | |
105 | for source in &message.source { | |
106 | if let AuthenticationSource::Instance { | |
107 | instance, | |
108 | signature, | |
109 | } = source | |
110 | { | |
111 | let public_key = key_cache.get(&instance).await.unwrap(); | |
112 | let public_key = RsaPublicKey::from_pkcs1_pem(&public_key).unwrap(); | |
113 | let verifying_key = VerifyingKey::<Sha256>::new(public_key); | |
114 | ||
115 | if verifying_key | |
116 | .verify( | |
117 | &message.payload, | |
118 | &Signature::try_from(signature.as_ref()).unwrap(), | |
119 | ) | |
120 | .is_ok() | |
121 | { | |
122 | verified_instance = | |
123 | Some(AuthenticatedInstance::new(instance.clone())); | |
124 | ||
125 | break; | |
126 | } | |
127 | } | |
128 | } | |
129 | ||
130 | verified_instance | |
131 | }; | |
132 | ||
133 | let user = { | |
134 | let mut verified_user = None; | |
135 | if let Some(verified_instance) = &instance { | |
136 | for source in &message.source { | |
137 | if let AuthenticationSource::User { user, token } = source { | |
138 | // Get token | |
139 | let public_key = key_cache.get(&verified_instance).await.unwrap(); | |
140 | ||
141 | let token: TokenData<UserTokenMetadata> = jsonwebtoken::decode( | |
142 | token.as_ref(), | |
143 | &DecodingKey::from_rsa_pem(public_key.as_bytes()).unwrap(), | |
144 | &Validation::new(jsonwebtoken::Algorithm::RS256), | |
145 | ) | |
146 | .unwrap(); | |
147 | ||
148 | if token.claims.generated_for != *verified_instance.deref() { | |
149 | // Nope! | |
150 | break; | |
151 | } | |
152 | ||
153 | if token.claims.user != *user { | |
154 | // Nope! | |
155 | break; | |
156 | } | |
157 | ||
158 | verified_user = Some(AuthenticatedUser::new(user.clone())); | |
159 | break; | |
160 | } | |
161 | } | |
162 | } | |
163 | ||
164 | verified_user | |
165 | }; | |
166 | ||
86 | 167 | let message: GiteratedMessage<AnyObject, AnyOperation> = message.into_message(); |
87 | 168 | |
169 | operation_state.user = user; | |
170 | operation_state.instance = instance; | |
171 | ||
88 | 172 | let result = backend |
89 | 173 | .object_operation( |
90 | 174 | message.object, |
@@ -94,6 +178,10 @@ pub async fn connection_wrapper( | ||
94 | 178 | ) |
95 | 179 | .await; |
96 | 180 | |
181 | // Asking for exploits here | |
182 | operation_state.user = None; | |
183 | operation_state.instance = None; | |
184 | ||
97 | 185 | // Map result to Vec<u8> on both |
98 | 186 | let result = match result { |
99 | 187 | Ok(result) => Ok(serde_json::to_vec(&result).unwrap()), |
giterated-daemon/src/database_backend/handler.rs
@@ -12,8 +12,8 @@ use giterated_models::{ | ||
12 | 12 | Commit, DefaultBranch, Description, LatestCommit, Repository, |
13 | 13 | RepositoryCommitBeforeRequest, RepositoryDiff, RepositoryDiffPatchRequest, |
14 | 14 | RepositoryDiffRequest, RepositoryFile, RepositoryFileFromIdRequest, |
15 | RepositoryFileInspectRequest, RepositoryInfoRequest, RepositorySummary, RepositoryView, | |
16 | Visibility, RepositoryFileFromPathRequest, | |
15 | RepositoryFileFromPathRequest, RepositoryFileInspectRequest, RepositoryInfoRequest, | |
16 | RepositorySummary, RepositoryView, Visibility, | |
17 | 17 | }, |
18 | 18 | settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError}, |
19 | 19 | user::{User, UserRepositoriesRequest}, |
giterated-daemon/src/database_backend/mod.rs
@@ -19,9 +19,9 @@ use crate::backend::{RepositoryBackend, UserBackend}; | ||
19 | 19 | use self::handler::{ |
20 | 20 | instance_authentication_request, instance_create_repository_request, |
21 | 21 | instance_registration_request, repository_commit_before, repository_diff, |
22 | repository_diff_patch, repository_file_from_id, repository_get_setting, repository_get_value, | |
23 | repository_info, repository_set_setting, user_get_repositories, user_get_setting, | |
24 | user_get_value, user_set_setting, repository_file_from_path, | |
22 | repository_diff_patch, repository_file_from_id, repository_file_from_path, | |
23 | repository_get_setting, repository_get_value, repository_info, repository_set_setting, | |
24 | user_get_repositories, user_get_setting, user_get_value, user_set_setting, | |
25 | 25 | }; |
26 | 26 | |
27 | 27 | #[derive(Clone, Debug)] |
@@ -124,8 +124,8 @@ mod test { | ||
124 | 124 | use giterated_models::repository::{ |
125 | 125 | Commit, Description, Repository, RepositoryCommitBeforeRequest, RepositoryDiff, |
126 | 126 | RepositoryDiffPatchRequest, RepositoryDiffRequest, RepositoryFile, |
127 | RepositoryFileFromIdRequest, RepositoryFileInspectRequest, RepositorySummary, | |
128 | RepositoryTreeEntry, RepositoryFileFromPathRequest, | |
127 | RepositoryFileFromIdRequest, RepositoryFileFromPathRequest, RepositoryFileInspectRequest, | |
128 | RepositorySummary, RepositoryTreeEntry, | |
129 | 129 | }; |
130 | 130 | use giterated_models::settings::AnySetting; |
131 | 131 | use giterated_models::user::{DisplayName, User}; |
giterated-daemon/src/main.rs
@@ -96,6 +96,8 @@ async fn main() -> Result<(), Error> { | ||
96 | 96 | let operation_state = { |
97 | 97 | StackOperationState { |
98 | 98 | giterated_backend: backend_wrapper, |
99 | instance: None, | |
100 | user: None, | |
99 | 101 | } |
100 | 102 | }; |
101 | 103 |
giterated-models/src/authenticated.rs
@@ -1,4 +1,4 @@ | ||
1 | use std::fmt::Debug; | |
1 | use std::{fmt::Debug, sync::Arc}; | |
2 | 2 | |
3 | 3 | use rsa::{ |
4 | 4 | pkcs1::DecodeRsaPrivateKey, |
@@ -27,7 +27,7 @@ pub struct UserTokenMetadata { | ||
27 | 27 | |
28 | 28 | #[derive(Debug)] |
29 | 29 | pub struct Authenticated<O: GiteratedObject, D: GiteratedOperation<O>> { |
30 | pub source: Vec<Box<dyn AuthenticationSourceProvider + Send + Sync>>, | |
30 | pub source: Vec<Arc<dyn AuthenticationSourceProviders + Send + Sync>>, | |
31 | 31 | pub message: GiteratedMessage<O, D>, |
32 | 32 | } |
33 | 33 | |
@@ -87,12 +87,11 @@ impl<O: GiteratedObject, D: GiteratedOperation<O>> Authenticated<O, D> { | ||
87 | 87 | } |
88 | 88 | } |
89 | 89 | |
90 | pub fn append_authentication<P: AuthenticationSourceProvider + 'static + Send + Sync>( | |
90 | pub fn append_authentication( | |
91 | 91 | &mut self, |
92 | authentication: P, | |
92 | authentication: Arc<dyn AuthenticationSourceProviders + Send + Sync>, | |
93 | 93 | ) { |
94 | self.source | |
95 | .push(Box::new(authentication) as Box<dyn AuthenticationSourceProvider + Send + Sync>); | |
94 | self.source.push(authentication); | |
96 | 95 | } |
97 | 96 | |
98 | 97 | pub fn into_payload(mut self) -> AuthenticatedPayload { |
@@ -104,7 +103,8 @@ impl<O: GiteratedObject, D: GiteratedOperation<O>> Authenticated<O, D> { | ||
104 | 103 | source: self |
105 | 104 | .source |
106 | 105 | .drain(..) |
107 | .map(|provider| provider.as_ref().authenticate(&payload)) | |
106 | .map(|provider| provider.as_ref().authenticate_all(&payload)) | |
107 | .flatten() | |
108 | 108 | .collect::<Vec<_>>(), |
109 | 109 | payload, |
110 | 110 | } |
giterated-models/src/repository/operations.rs
@@ -231,10 +231,7 @@ impl<S: Clone + Send + Sync, B: ObjectBackend<S> + std::fmt::Debug> Object<'_, S | ||
231 | 231 | operation_state: &S, |
232 | 232 | ) -> Result<RepositoryFile, OperationError<RepositoryError>> { |
233 | 233 | self.request::<RepositoryFileFromPathRequest>( |
234 | RepositoryFileFromPathRequest { | |
235 | rev, | |
236 | path, | |
237 | }, | |
234 | RepositoryFileFromPathRequest { rev, path }, | |
238 | 235 | operation_state, |
239 | 236 | ) |
240 | 237 | .await |
giterated-stack/src/lib.rs
@@ -1,7 +1,7 @@ | ||
1 | 1 | pub mod handler; |
2 | 2 | pub mod state; |
3 | 3 | |
4 | use std::{collections::HashMap, future::Future, pin::Pin, str::FromStr, sync::Arc}; | |
4 | use std::{collections::HashMap, future::Future, ops::Deref, pin::Pin, str::FromStr, sync::Arc}; | |
5 | 5 | |
6 | 6 | use futures_util::FutureExt; |
7 | 7 | use giterated_models::{ |
@@ -394,6 +394,42 @@ impl FromOperationState for StackOperationState { | ||
394 | 394 | #[derive(Clone)] |
395 | 395 | pub struct StackOperationState { |
396 | 396 | pub giterated_backend: BackendWrapper, |
397 | pub instance: Option<AuthenticatedInstance>, | |
398 | pub user: Option<AuthenticatedUser>, | |
399 | } | |
400 | ||
401 | #[derive(Clone, Debug)] | |
402 | pub struct AuthenticatedInstance(Instance); | |
403 | ||
404 | impl AuthenticatedInstance { | |
405 | pub fn new(instance: Instance) -> Self { | |
406 | AuthenticatedInstance(instance) | |
407 | } | |
408 | } | |
409 | ||
410 | impl Deref for AuthenticatedInstance { | |
411 | type Target = Instance; | |
412 | ||
413 | fn deref(&self) -> &Self::Target { | |
414 | &self.0 | |
415 | } | |
416 | } | |
417 | ||
418 | #[derive(Clone, Debug)] | |
419 | pub struct AuthenticatedUser(User); | |
420 | ||
421 | impl AuthenticatedUser { | |
422 | pub fn new(user: User) -> Self { | |
423 | AuthenticatedUser(user) | |
424 | } | |
425 | } | |
426 | ||
427 | impl Deref for AuthenticatedUser { | |
428 | type Target = User; | |
429 | ||
430 | fn deref(&self) -> &Self::Target { | |
431 | &self.0 | |
432 | } | |
397 | 433 | } |
398 | 434 | |
399 | 435 | #[derive(Clone)] |