@@ -40,110 +40,97 @@ class Storages::ProjectStoragesController < ApplicationController
40
40
before_action :render_403 , unless : -> { User . current . allowed_in_project? ( :view_file_links , @project ) }
41
41
no_authorization_required! :open
42
42
43
- # rubocop:disable Metrics/AbcSize
43
+ before_action :ensure_remote_identity , only : :open
44
+ before_action :ensure_folder_created , only : :open
45
+ before_action :ensure_folder_permissions , only : :open
46
+
44
47
def open
45
- if @object . project_folder_automatic?
46
- @storage = @object . storage
47
- # check if user "see" project_folder
48
- if @object . project_folder_id . present?
49
- ::Storages ::Peripherals ::Registry
50
- . resolve ( "#{ @storage } .queries.file_info" )
51
- . call ( storage : @storage , auth_strategy :, file_id : @object . project_folder_id )
52
- . match (
53
- on_success : user_can_read_project_folder ,
54
- on_failure : user_can_not_read_project_folder
55
- )
56
- else
57
- respond_to do |format |
58
- format . turbo_stream { head :no_content }
59
- format . html do
60
- redirect_to_project_overview_with_modal
61
- end
62
- end
63
- end
64
- else
65
- redirect_to api_v3_project_storage_open
66
- end
48
+ @project_storage . open ( current_user ) . match (
49
+ on_success : -> ( url ) { redirect_to url , allow_other_host : true } ,
50
+ on_failure : -> ( error ) { raise error }
51
+ )
67
52
end
68
53
69
- # rubocop:enable Metrics/AbcSize
70
-
71
54
private
72
55
73
- def auth_strategy
74
- Storages ::Peripherals ::Registry . resolve ( " #{ @storage } .authentication.user_bound" ) . call ( user : current_user , storage : @storage )
75
- end
56
+ def ensure_remote_identity
57
+ selector = Storages ::Peripherals ::StorageInteraction :: AuthenticationMethodSelector . new ( user : current_user , storage :)
58
+ return unless selector . storage_oauth?
76
59
77
- def user_can_read_project_folder
78
- -> ( _ ) do
79
- respond_to do |format |
80
- format . turbo_stream do
81
- render (
82
- turbo_stream : OpTurbo ::StreamComponent . new (
83
- action : :update ,
84
- target : Storages ::OpenProjectStorageModalComponent . dialog_body_id ,
85
- template : Storages ::OpenProjectStorageModalComponent ::Body . new ( :success ) . render_in ( view_context )
86
- ) . render_in ( view_context )
87
- )
88
- end
89
- format . html { redirect_to api_v3_project_storage_open }
90
- end
60
+ case Storages ::Peripherals ::StorageInteraction ::Authentication . authorization_state ( storage :, user : current_user )
61
+ when :not_connected , :failed_authorization
62
+ redirect_to ensure_connection_url
63
+ when :error
64
+ show_error ( I18n . t ( "project_storages.open.remote_identity_error" ) )
91
65
end
92
66
end
93
67
94
- def user_can_not_read_project_folder
95
- -> ( result ) do
96
- respond_to do |format |
97
- format . turbo_stream { head :no_content }
98
- format . html do
99
- case result . code
100
- when :unauthorized
101
- redirect_to ( storage_fallback_url , allow_other_host : true )
102
- when :forbidden
103
- redirect_to_project_overview_with_modal
104
- end
105
- end
106
- end
68
+ def ensure_folder_created
69
+ return unless @project_storage . project_folder_automatic?
70
+ return if @project_storage . project_folder_id . present?
71
+
72
+ folder_create_service . new ( storage :, project_storages : project_storage_scope ) . call . on_failure do |result |
73
+ return show_error ( result . errors . full_messages )
107
74
end
75
+ @project_storage . reload
108
76
end
109
77
110
- def storage_fallback_url
111
- selector = Storages ::Peripherals ::StorageInteraction ::AuthenticationMethodSelector . new ( user : current_user , storage : @storage )
112
- if selector . sso?
113
- # Maybe the user just can't read folder because they are not provisioned in (Nextcloud) storage. We redirect them
114
- # to the storage and leave error handling up to storage. Ideally they will login to the storage and thus prevent
115
- # the same error in the future.
116
- # This would not work for OneDrive, but for OneDrive we don't have SSO (yet).
117
- res = Storages ::Peripherals ::Registry . resolve ( "#{ @storage } .queries.open_file_link" ) . call (
118
- storage : @storage ,
119
- auth_strategy :,
120
- file_id : @object . project_folder_id
121
- )
122
- res . result_or { |errors | raise "Could not redirect SSO user to storage: #{ errors } " }
123
- else
124
- oauth_clients_ensure_connection_url (
125
- oauth_client_id : @storage . oauth_client . client_id ,
126
- storage_id : @storage . id ,
127
- destination_url : request . url
128
- )
78
+ def ensure_folder_permissions
79
+ return unless @project_storage . project_folder_automatic?
80
+
81
+ result = test_folder_access
82
+ return if result . success? || result . errors . code != :forbidden
83
+
84
+ # Note: The time this operation takes may still scale with the number of users.
85
+ # If this becomes a problem, we will have to update downstream code to allow changing permissions for a few users instead of
86
+ # requiring to always set all permissions.
87
+ folder_permissions_service . new ( storage :, project_storages : project_storage_scope ) . call . on_failure do |r |
88
+ return show_error ( r . errors . full_messages )
129
89
end
130
90
end
131
91
132
- def redirect_to_project_overview_with_modal
133
- redirect_to (
134
- project_overview_path ( project_id : @project . identifier ) ,
135
- op_modal : {
136
- component : Storages ::OpenProjectStorageModalComponent . name ,
137
- parameters : {
138
- project_storage_open_url : request . path ,
139
- redirect_url : api_v3_project_storage_open ,
140
- state : :waiting
141
- }
142
- }
92
+ def ensure_connection_url
93
+ oauth_clients_ensure_connection_url (
94
+ oauth_client_id : storage . oauth_client . client_id ,
95
+ storage_id : storage . id ,
96
+ destination_url : request . url
97
+ )
98
+ end
99
+
100
+ def folder_create_service
101
+ Storages ::Peripherals ::Registry . resolve ( "#{ storage } .services.folder_create" )
102
+ end
103
+
104
+ def folder_permissions_service
105
+ Storages ::Peripherals ::Registry . resolve ( "#{ storage } .services.folder_permissions" )
106
+ end
107
+
108
+ def file_info
109
+ Storages ::Peripherals ::Registry . resolve ( "#{ storage } .queries.file_info" )
110
+ end
111
+
112
+ def user_bound
113
+ Storages ::Peripherals ::Registry . resolve ( "#{ storage } .authentication.user_bound" )
114
+ end
115
+
116
+ def storage
117
+ @object . storage
118
+ end
119
+
120
+ def project_storage_scope
121
+ Storages ::ProjectStorage . where ( id : @project_storage . id )
122
+ end
123
+
124
+ def test_folder_access
125
+ file_info . call (
126
+ storage :,
127
+ auth_strategy : user_bound . call ( storage :, user : current_user ) ,
128
+ file_id : @project_storage . project_folder_id
143
129
)
144
130
end
145
131
146
- def api_v3_project_storage_open
147
- ::API ::V3 ::Utilities ::PathHelper ::ApiV3Path . project_storage_open ( @object . id )
132
+ def show_error ( message )
133
+ flash [ :error ] = message
134
+ redirect_back ( fallback_location : project_path ( id : @project_storage . project_id ) )
148
135
end
149
136
end
0 commit comments