77import org .springframework .util .StringUtils ;
88
99import com .google .api .services .drive .Drive ;
10+ import com .google .api .services .drive .model .File ;
1011import com .google .auth .oauth2 .ServiceAccountCredentials ;
1112
1213import gg .agit .konect .domain .user .enums .Provider ;
2324public class GoogleSheetPermissionService {
2425
2526 private final ServiceAccountCredentials serviceAccountCredentials ;
27+ private final Drive googleDriveService ;
2628 private final GoogleSheetsConfig googleSheetsConfig ;
2729 private final UserOAuthAccountRepository userOAuthAccountRepository ;
2830
31+ public void validateRequesterAccessAndTryGrantServiceAccountWriterAccess (
32+ Integer requesterId ,
33+ String spreadsheetId
34+ ) {
35+ String refreshToken = requireRefreshToken (requesterId );
36+ Drive userDriveService = buildUserDriveService (refreshToken , requesterId );
37+ validateRequesterSpreadsheetAccess (userDriveService , requesterId , spreadsheetId );
38+ boolean granted = tryGrantServiceAccountWriterAccess (userDriveService , requesterId , spreadsheetId );
39+ if (!granted ) {
40+ requireServiceAccountSpreadsheetAccess (spreadsheetId , requesterId );
41+ }
42+ }
43+
2944 public boolean tryGrantServiceAccountWriterAccess (Integer requesterId , String spreadsheetId ) {
30- String refreshToken = userOAuthAccountRepository
31- .findByUserIdAndProvider (requesterId , Provider .GOOGLE )
32- .map (account -> account .getGoogleDriveRefreshToken ())
33- .filter (StringUtils ::hasText )
34- .orElse (null );
45+ String refreshToken = resolveRefreshToken (requesterId );
3546
3647 if (refreshToken == null ) {
3748 log .warn (
@@ -41,14 +52,35 @@ public boolean tryGrantServiceAccountWriterAccess(Integer requesterId, String sp
4152 return false ;
4253 }
4354
44- Drive userDriveService ;
45- try {
46- userDriveService = googleSheetsConfig .buildUserDriveService (refreshToken );
47- } catch (IOException | GeneralSecurityException e ) {
48- log .error ("Failed to build user Drive service. requesterId={}" , requesterId , e );
49- throw CustomException .of (ApiResponseCode .FAILED_INIT_GOOGLE_DRIVE );
50- }
55+ Drive userDriveService = buildUserDriveService (refreshToken , requesterId );
56+ return tryGrantServiceAccountWriterAccess (userDriveService , requesterId , spreadsheetId );
57+ }
58+
59+ private String requireRefreshToken (Integer requesterId ) {
60+ return userOAuthAccountRepository .findByUserIdAndProvider (requesterId , Provider .GOOGLE )
61+ .map (account -> account .getGoogleDriveRefreshToken ())
62+ .filter (StringUtils ::hasText )
63+ .orElseThrow (() -> {
64+ log .warn (
65+ "Rejecting spreadsheet registration because Google Drive OAuth is not connected. requesterId={}" ,
66+ requesterId
67+ );
68+ return CustomException .of (ApiResponseCode .NOT_FOUND_GOOGLE_DRIVE_AUTH );
69+ });
70+ }
71+
72+ private String resolveRefreshToken (Integer requesterId ) {
73+ return userOAuthAccountRepository .findByUserIdAndProvider (requesterId , Provider .GOOGLE )
74+ .map (account -> account .getGoogleDriveRefreshToken ())
75+ .filter (StringUtils ::hasText )
76+ .orElse (null );
77+ }
5178
79+ private boolean tryGrantServiceAccountWriterAccess (
80+ Drive userDriveService ,
81+ Integer requesterId ,
82+ String spreadsheetId
83+ ) {
5284 try {
5385 GoogleDrivePermissionHelper .ensureServiceAccountPermission (
5486 userDriveService ,
@@ -91,6 +123,105 @@ public boolean tryGrantServiceAccountWriterAccess(Integer requesterId, String sp
91123 }
92124 }
93125
126+ private Drive buildUserDriveService (String refreshToken , Integer requesterId ) {
127+ try {
128+ return googleSheetsConfig .buildUserDriveService (refreshToken );
129+ } catch (IOException | GeneralSecurityException e ) {
130+ log .error ("Failed to build user Drive service. requesterId={}" , requesterId , e );
131+ throw CustomException .of (ApiResponseCode .FAILED_INIT_GOOGLE_DRIVE );
132+ }
133+ }
134+
135+ private void validateRequesterSpreadsheetAccess (
136+ Drive userDriveService ,
137+ Integer requesterId ,
138+ String spreadsheetId
139+ ) {
140+ try {
141+ File file = userDriveService .files ().get (spreadsheetId )
142+ .setFields ("id" )
143+ .setSupportsAllDrives (true )
144+ .execute ();
145+ if (file == null || !StringUtils .hasText (file .getId ())) {
146+ throw GoogleSheetApiExceptionHelper .accessDenied ();
147+ }
148+ } catch (IOException e ) {
149+ if (GoogleSheetApiExceptionHelper .isInvalidGrant (e )) {
150+ log .warn (
151+ "Google Drive OAuth token is invalid while validating spreadsheet access. requesterId={}, "
152+ + "spreadsheetId={}, cause={}" ,
153+ requesterId ,
154+ spreadsheetId ,
155+ GoogleSheetApiExceptionHelper .extractDetail (e )
156+ );
157+ throw GoogleSheetApiExceptionHelper .invalidGoogleDriveAuth (e );
158+ }
159+
160+ if (GoogleSheetApiExceptionHelper .isAuthFailure (e )) {
161+ log .warn (
162+ "Google Drive OAuth auth failure while validating spreadsheet access. requesterId={}, "
163+ + "spreadsheetId={}, cause={}" ,
164+ requesterId ,
165+ spreadsheetId ,
166+ GoogleSheetApiExceptionHelper .extractDetail (e )
167+ );
168+ throw GoogleSheetApiExceptionHelper .invalidGoogleDriveAuth (e );
169+ }
170+
171+ if (GoogleSheetApiExceptionHelper .isAccessDenied (e )
172+ || GoogleSheetApiExceptionHelper .isNotFound (e )) {
173+ log .warn (
174+ "Requester has no spreadsheet access. requesterId={}, spreadsheetId={}, cause={}" ,
175+ requesterId ,
176+ spreadsheetId ,
177+ e .getMessage ()
178+ );
179+ throw GoogleSheetApiExceptionHelper .accessDenied ();
180+ }
181+
182+ log .error (
183+ "Unexpected error while validating requester spreadsheet access. requesterId={}, spreadsheetId={}" ,
184+ requesterId ,
185+ spreadsheetId ,
186+ e
187+ );
188+ throw CustomException .of (ApiResponseCode .FAILED_SYNC_GOOGLE_SHEET );
189+ }
190+ }
191+
192+ private void requireServiceAccountSpreadsheetAccess (String spreadsheetId , Integer requesterId ) {
193+ try {
194+ File file = googleDriveService .files ().get (spreadsheetId )
195+ .setFields ("id" )
196+ .setSupportsAllDrives (true )
197+ .execute ();
198+ if (file == null || !StringUtils .hasText (file .getId ())) {
199+ throw GoogleSheetApiExceptionHelper .accessDenied ();
200+ }
201+ } catch (IOException e ) {
202+ if (GoogleSheetApiExceptionHelper .isAccessDenied (e )
203+ || GoogleSheetApiExceptionHelper .isNotFound (e )) {
204+ log .warn (
205+ "Service account has no spreadsheet access after auto-share failed. requesterId={}, "
206+ + "spreadsheetId={}, cause={}" ,
207+ requesterId ,
208+ spreadsheetId ,
209+ e .getMessage ()
210+ );
211+ throw GoogleSheetApiExceptionHelper .accessDenied ();
212+ }
213+
214+ log .error (
215+ "Unexpected error while re-checking service account spreadsheet access. requesterId={}, "
216+ + "spreadsheetId={}" ,
217+ requesterId ,
218+ spreadsheetId ,
219+ e
220+ );
221+ throw CustomException .of (ApiResponseCode .FAILED_SYNC_GOOGLE_SHEET );
222+ }
223+ }
224+
94225 private String getServiceAccountEmail () {
95226 return serviceAccountCredentials .getClientEmail ();
96227 }
0 commit comments