1010use App \Models \Invitation ;
1111use App \Models \Role ;
1212use App \Models \Tenant ;
13+ use App \Models \TimelineItem ;
1314use App \Models \YnhFramework ;
1415use App \Rules \IsValidCollectionName ;
1516use App \User ;
@@ -31,7 +32,8 @@ class ProcessIncomingEmails implements ShouldQueue
3132{
3233 use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
3334
34- const string SENDER = 'cyberbuddy@cywise.io ' ;
35+ const string SENDER_CYBERBUDDY = 'cyberbuddy@cywise.io ' ;
36+ const string SENDER_MEMEX = 'memex@cywise.io ' ;
3537
3638 public $ tries = 1 ;
3739 public $ maxExceptions = 1 ;
@@ -69,8 +71,10 @@ public function handle()
6971 $ from = $ message ->getFrom ()->all ();
7072 $ cc = $ message ->getCc ()->all ();
7173 $ bcc = $ message ->getBcc ()->all ();
74+ $ isCyberBuddy = collect ($ to )->contains (self ::SENDER_CYBERBUDDY );
75+ $ isMemex = collect ($ to )->contains (self ::SENDER_MEMEX );
7276
73- if (!collect ( $ to )-> contains ( self :: SENDER ) ) {
77+ if (!$ isCyberBuddy && ! $ isMemex ) {
7478 continue ;
7579 }
7680 if (count ($ from ) !== 1 ) {
@@ -84,48 +88,7 @@ public function handle()
8488 $ address = $ from [0 ];
8589
8690 // Create shadow profile
87- /** @var User $user */
88- $ user = User::where ('email ' , $ address ->mail )->first ();
89- if ($ user ) {
90- Auth::login ($ user ); // otherwise the tenant will not be properly set
91- } else {
92- /** @var Invitation $invitation */
93- $ invitation = Invitation::where ('email ' , $ address ->mail )->first ();
94-
95- if (!$ invitation ) {
96- $ invitation = InvitationProxy::createInvitation ($ address ->mail , "J. Doe " );
97- }
98-
99- /** @var Tenant $tenant */
100- $ tenant = Tenant::create (['name ' => Str::random ()]);
101- $ user = $ invitation ->createUser ([
102- 'password ' => Str::random (64 ),
103- 'tenant_id ' => $ tenant ->id ,
104- 'type ' => UserType::CLIENT (),
105- 'terms_accepted ' => true ,
106- ]);
107-
108- $ user ->syncRoles (Role::ADMINISTRATOR , Role::LIMITED_ADMINISTRATOR , Role::BASIC_END_USER );
109-
110- Auth::login ($ user ); // otherwise the tenant will not be properly set
111-
112- // Create shadow collections for some frameworks
113- $ frameworks = \App \Models \YnhFramework::all ();
114-
115- foreach ($ frameworks as $ framework ) {
116- if ($ framework ->file === 'seeds/frameworks/anssi/anssi-genai-security-recommendations-1.0.jsonl ' ) {
117- $ this ->importFramework ($ framework , 20 );
118- } else if ($ framework ->file === 'seeds/frameworks/anssi/anssi-guide-hygiene-detail.jsonl ' ) {
119- $ this ->importFramework ($ framework , 10 );
120- } else if ($ framework ->file === 'seeds/frameworks/gdpr/gdpr.jsonl ' ) {
121- $ this ->importFramework ($ framework , 30 );
122- } else if ($ framework ->file === 'seeds/frameworks/dora/dora.jsonl ' ) {
123- $ this ->importFramework ($ framework , 50 );
124- } else if ($ framework ->file === 'seeds/frameworks/nis2/nis2-directive.jsonl ' ) {
125- $ this ->importFramework ($ framework , 40 );
126- }
127- }
128- }
91+ $ user = $ this ->getOrCreateUser ($ address ->mail );
12992
13093 // Ensure all prompts are properly loaded
13194 /* if (Prompt::count() >= 4) {
@@ -138,85 +101,10 @@ public function handle()
138101 if (File::where ('is_deleted ' , false )->get ()->contains (fn (File $ file ) => !$ file ->is_embedded )) {
139102 Log::warning ($ message ->getSubject ());
140103 Log::warning ("Some collections are not ready yet. Skipping email processing for now. " );
141- continue ;
142- }
143-
144- // Extract the thread id in order to be able to load the existing conversation
145- // If the thread id cannot be found, a new conversation is created
146- $ threadId = null ;
147- $ matches = [];
148- preg_match_all ("/\s*thread_id=(?<threadid>[a-zA-Z0-9]{10})\s*/i " , $ message ->getTextBody (), $ matches , PREG_SET_ORDER );
149-
150- foreach ($ matches as $ match ) {
151- if (!empty ($ match ['threadid ' ])) {
152- $ threadId = $ match ['threadid ' ];
153- break ;
154- }
155- }
156- if (empty ($ threadId )) {
157- $ threadId = Str::random (10 );
158- }
159-
160- /** @var Conversation $conversation */
161- $ conversation = Conversation::where ('thread_id ' , $ threadId )
162- ->where ('format ' , Conversation::FORMAT_V1 )
163- ->where ('created_by ' , $ user ?->id)
164- ->first ();
165-
166- $ conversation = $ conversation ?? Conversation::create ([
167- 'thread_id ' => $ threadId ,
168- 'dom ' => json_encode ([]),
169- 'autosaved ' => true ,
170- 'created_by ' => $ user ?->id,
171- 'format ' => Conversation::FORMAT_V1 ,
172- ]);
173-
174- // Remove previous messages i.e. rows starting with >
175- $ body = trim (preg_replace ("/^(>.*)|(On\s+.*\s+wrote:)[ \n\r]?$/im " , '' , $ message ->getTextBody ()));
176-
177- Log::debug ('subject= ' . $ message ->getSubject ()->all ()[0 ]);
178- Log::debug ('body= ' . $ body );
179-
180- // Call CyberBuddy
181- $ request = new ConverseRequest ();
182- $ request ->replace ([
183- 'thread_id ' => $ threadId ,
184- 'directive ' => $ body ,
185- ]);
186-
187- $ controller = new CyberBuddyNextGenController ();
188- $ response = $ controller ->converse ($ request , true );
189- $ json = json_decode ($ response ->content (), true );
190- $ subject = $ message ->getSubject ()[0 ];
191- $ body = $ json ['answer ' ]['html ' ] ?? '' ;
192-
193- EndVulnsScanListener::sendEmail (
194- self ::SENDER ,
195- $ address ->mail ,
196- "Re: {$ subject }" ,
197- "CyberBuddy vous répond ! " ,
198- "
199- {$ body }
200- <p>Pour importer tes propres documents et profiter pleinement des capacités de CyberBuddy, ton assistant Cyber, finalise ton inscription à Cywise :</p>
201- " ,
202- route ('password.reset ' , [
203- 'token ' => app (PasswordBroker::class)->createToken ($ user ),
204- 'email ' => $ user ->email ,
205- 'reason ' => 'Finalisez votre inscription en créant un mot de passe ' ,
206- 'action ' => 'Créer mon mot de passe ' ,
207- ]),
208- "je me connecte à Cywise " ,
209- "
210- <p>Je reste à ta disposition pour toute question ou assistance supplémentaire. Merci encore pour ta confiance en Cywise !</p>
211- <p>Bien à toi,</p>
212- <p>CyberBuddy</p>
213- <span style='color:white'>thread_id= {$ threadId }</span>
214- " ,
215- );
216-
217- if (!$ message ->move ('CyberBuddy ' )) {
218- Log::error ($ message ->getSubject ());
219- Log::error ('Message could not be moved! ' );
104+ } else if ($ isCyberBuddy ) {
105+ $ this ->cyberBuddy ($ user , $ message );
106+ } else if ($ isMemex ) {
107+ $ this ->memex ($ user , $ message );
220108 }
221109 }
222110 }
@@ -228,6 +116,54 @@ public function handle()
228116 }
229117 }
230118
119+ private function getOrCreateUser (string $ email ): User
120+ {
121+ /** @var User $user */
122+ $ user = User::where ('email ' , $ email )->first ();
123+ if ($ user ) {
124+ Auth::login ($ user ); // otherwise the tenant will not be properly set
125+ } else {
126+
127+ /** @var Invitation $invitation */
128+ $ invitation = Invitation::where ('email ' , $ email )->first ();
129+
130+ if (!$ invitation ) {
131+ $ invitation = InvitationProxy::createInvitation ($ email , Str::before ($ email , '@ ' ));
132+ }
133+
134+ /** @var Tenant $tenant */
135+ $ tenant = Tenant::create (['name ' => Str::random ()]);
136+ $ user = $ invitation ->createUser ([
137+ 'password ' => Str::random (64 ),
138+ 'tenant_id ' => $ tenant ->id ,
139+ 'type ' => UserType::CLIENT (),
140+ 'terms_accepted ' => true ,
141+ ]);
142+
143+ $ user ->syncRoles (Role::ADMINISTRATOR , Role::LIMITED_ADMINISTRATOR , Role::BASIC_END_USER );
144+
145+ Auth::login ($ user ); // otherwise the tenant will not be properly set
146+
147+ // Create shadow collections for some frameworks
148+ $ frameworks = \App \Models \YnhFramework::all ();
149+
150+ foreach ($ frameworks as $ framework ) {
151+ if ($ framework ->file === 'seeds/frameworks/anssi/anssi-genai-security-recommendations-1.0.jsonl ' ) {
152+ $ this ->importFramework ($ framework , 20 );
153+ } else if ($ framework ->file === 'seeds/frameworks/anssi/anssi-guide-hygiene-detail.jsonl ' ) {
154+ $ this ->importFramework ($ framework , 10 );
155+ } else if ($ framework ->file === 'seeds/frameworks/gdpr/gdpr.jsonl ' ) {
156+ $ this ->importFramework ($ framework , 30 );
157+ } else if ($ framework ->file === 'seeds/frameworks/dora/dora.jsonl ' ) {
158+ $ this ->importFramework ($ framework , 50 );
159+ } else if ($ framework ->file === 'seeds/frameworks/nis2/nis2-directive.jsonl ' ) {
160+ $ this ->importFramework ($ framework , 40 );
161+ }
162+ }
163+ }
164+ return $ user ;
165+ }
166+
231167 private function importFramework (YnhFramework $ framework , int $ priority ): void
232168 {
233169 /** @var \App\Models\Collection $collection */
@@ -256,4 +192,94 @@ private function importFramework(YnhFramework $framework, int $priority): void
256192 );
257193 $ url = \App \Http \Controllers \CyberBuddyController::saveUploadedFile ($ collection , $ file );
258194 }
195+
196+ private function cyberBuddy (User $ user , \Webklex \PHPIMAP \Message $ message )
197+ {
198+ // Extract the thread id in order to be able to load the existing conversation
199+ // If the thread id cannot be found, a new conversation is created
200+ $ threadId = null ;
201+ $ matches = [];
202+ preg_match_all ("/\s*thread_id=(?<threadid>[a-zA-Z0-9]{10})\s*/i " , $ message ->getTextBody (), $ matches , PREG_SET_ORDER );
203+
204+ foreach ($ matches as $ match ) {
205+ if (!empty ($ match ['threadid ' ])) {
206+ $ threadId = $ match ['threadid ' ];
207+ break ;
208+ }
209+ }
210+ if (empty ($ threadId )) {
211+ $ threadId = Str::random (10 );
212+ }
213+
214+ /** @var Conversation $conversation */
215+ $ conversation = Conversation::where ('thread_id ' , $ threadId )
216+ ->where ('format ' , Conversation::FORMAT_V1 )
217+ ->where ('created_by ' , $ user ->id )
218+ ->first ();
219+
220+ $ conversation = $ conversation ?? Conversation::create ([
221+ 'thread_id ' => $ threadId ,
222+ 'dom ' => json_encode ([]),
223+ 'autosaved ' => true ,
224+ 'created_by ' => $ user ->id ,
225+ 'format ' => Conversation::FORMAT_V1 ,
226+ ]);
227+
228+ // Remove previous messages i.e. rows starting with >
229+ $ body = trim (preg_replace ("/^(>.*)|(On\s+.*\s+wrote:)[ \n\r]?$/im " , '' , $ message ->getTextBody ()));
230+
231+ Log::debug ('subject= ' . $ message ->getSubject ()[0 ] ?? '' );
232+ Log::debug ('body= ' . $ body );
233+
234+ // Call CyberBuddy
235+ $ request = new ConverseRequest ();
236+ $ request ->replace ([
237+ 'thread_id ' => $ threadId ,
238+ 'directive ' => $ body ,
239+ ]);
240+
241+ $ controller = new CyberBuddyNextGenController ();
242+ $ response = $ controller ->converse ($ request , true );
243+ $ json = json_decode ($ response ->content (), true );
244+ $ subject = $ message ->getSubject ()[0 ] ?? '' ;
245+ $ body = $ json ['answer ' ]['html ' ] ?? '' ;
246+
247+ EndVulnsScanListener::sendEmail (
248+ self ::SENDER_CYBERBUDDY ,
249+ $ user ->email ,
250+ "Re: {$ subject }" ,
251+ "CyberBuddy vous répond ! " ,
252+ "
253+ {$ body }
254+ <p>Pour importer tes propres documents et profiter pleinement des capacités de CyberBuddy, ton assistant Cyber, finalise ton inscription à Cywise :</p>
255+ " ,
256+ route ('password.reset ' , [
257+ 'token ' => app (PasswordBroker::class)->createToken ($ user ),
258+ 'email ' => $ user ->email ,
259+ 'reason ' => 'Finalisez votre inscription en créant un mot de passe ' ,
260+ 'action ' => 'Créer mon mot de passe ' ,
261+ ]),
262+ "je me connecte à Cywise " ,
263+ "
264+ <p>Je reste à ta disposition pour toute question ou assistance supplémentaire. Merci encore pour ta confiance en Cywise !</p>
265+ <p>Bien à toi,</p>
266+ <p>CyberBuddy</p>
267+ <span style='color:white'>thread_id= {$ threadId }</span>
268+ " ,
269+ );
270+
271+ if (!$ message ->move ('CyberBuddy ' )) {
272+ Log::error ($ message ->getSubject ());
273+ Log::error ('Message could not be moved to the CyberBuddy folder! ' );
274+ }
275+ }
276+
277+ private function memex (User $ user , \Webklex \PHPIMAP \Message $ message )
278+ {
279+ $ item = TimelineItem::createNote ($ user ->id , $ message ->getTextBody (), $ message ->getSubject ()[0 ] ?? '' );
280+ if (!$ message ->move ('Memex ' )) {
281+ Log::error ($ message ->getSubject ());
282+ Log::error ('Message could not be moved to the Memex folder! ' );
283+ }
284+ }
259285}
0 commit comments