JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Add authentication back into the operation states

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨97a26fd

Showing ⁨⁨9⁩ changed files⁩ with ⁨⁨167⁩ insertions⁩ and ⁨⁨35⁩ deletions⁩

giterated-daemon/src/backend/git.rs

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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

View file
@@ -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)]