1
+ import 'package:beautiful_soup_dart/beautiful_soup.dart' ;
1
2
import 'package:dan_xi/model/person.dart' ;
2
3
import 'package:dan_xi/provider/settings_provider.dart' ;
3
4
import 'package:dan_xi/repository/fdu/uis_login_tool.dart' ;
4
5
import 'package:dan_xi/repository/cookie/independent_cookie_jar.dart' ;
5
6
import 'package:dan_xi/repository/cookie/readonly_cookie_jar.dart' ;
6
7
import 'package:dan_xi/util/io/dio_utils.dart' ;
8
+ import 'package:dan_xi/util/io/user_agent_interceptor.dart' ;
7
9
import 'package:dio/dio.dart' ;
10
+ import 'package:dio_cookie_manager/dio_cookie_manager.dart' ;
11
+ import 'package:flutter/cupertino.dart' ;
8
12
import 'package:flutter/foundation.dart' ;
9
13
10
14
class WebvpnRequestException implements Exception {
@@ -30,10 +34,13 @@ class WebvpnProxy {
30
34
// Cookies related with webvpn
31
35
static final ReadonlyCookieJar webvpnCookieJar = ReadonlyCookieJar ();
32
36
33
- static const String DIRECT_CONNECT_TEST_URL = "https://danta .fudan.edu.cn" ;
37
+ static const String DIRECT_CONNECT_TEST_URL = "https://forum .fudan.edu.cn" ;
34
38
35
- static const String WEBVPN_LOGIN_URL =
36
- "https://uis.fudan.edu.cn/authserver/login?service=https%3A%2F%2Fwebvpn.fudan.edu.cn%2Flogin%3Fcas_login%3Dtrue" ;
39
+ static const String WEBVPN_UIS_LOGIN_URL =
40
+ "https://uis.fudan.edu.cn/authserver/login?service=https%3A%2F%2Fid.fudan.edu.cn%2Fidp%2FthirdAuth%2Fcas" ;
41
+ static const String WEBVPN_ID_REQUEST_URL =
42
+ "https://id.fudan.edu.cn/idp/authCenter/authenticate?service=https%3A%2F%2Fwebvpn.fudan.edu.cn%2Flogin%3Fcas_login%3Dtrue" ;
43
+ static const String WEBVPN_LOGIN_URL = "https://webvpn.fudan.edu.cn/login" ;
37
44
38
45
static final Map <String , String > _vpnPrefix = {
39
46
"www.fduhole.com" :
@@ -82,9 +89,7 @@ class WebvpnProxy {
82
89
return false ;
83
90
}
84
91
85
- if (response.realUri
86
- .toString ()
87
- .startsWith ("https://webvpn.fudan.edu.cn/login" )) {
92
+ if (response.realUri.toString ().startsWith (WEBVPN_LOGIN_URL )) {
88
93
return false ;
89
94
}
90
95
@@ -119,21 +124,70 @@ class WebvpnProxy {
119
124
debugPrint ("Logging into WebVPN" );
120
125
121
126
try {
127
+ Dio newDio = DioUtils .newDioWithProxy ();
128
+ newDio.options = BaseOptions (
129
+ receiveDataWhenStatusError: true ,
130
+ connectTimeout: const Duration (seconds: 5 ),
131
+ receiveTimeout: const Duration (seconds: 5 ),
132
+ sendTimeout: const Duration (seconds: 5 ));
133
+ newDio.interceptors.add (UserAgentInterceptor (
134
+ userAgent: SettingsProvider .getInstance ().customUserAgent));
122
135
// Temporary cookie jar
123
136
IndependentCookieJar workJar = IndependentCookieJar ();
124
- loginSession =
125
- UISLoginTool .loginUIS (dio, WEBVPN_LOGIN_URL , workJar, _personInfo);
137
+ newDio.interceptors.add (CookieManager (workJar));
138
+
139
+ loginSession = _authenticateWebVPN (newDio, workJar, _personInfo);
126
140
await loginSession;
127
- // Clone from temp jar to our dedicated webvpn jar
141
+
128
142
webvpnCookieJar.cloneFrom (workJar);
129
143
isLoggedIn = true ;
144
+ } catch (e) {
145
+ debugPrint ("Failed to login to WebVPN: $e " );
146
+ isLoggedIn = false ;
147
+ rethrow ;
130
148
} finally {
131
149
loginSession = null ;
132
150
}
133
151
// Any exception thrown won't be catched and will be propagated to widgets
134
152
}
135
153
}
136
154
155
+ static Future <void > _authenticateWebVPN (
156
+ Dio dio, IndependentCookieJar jar, PersonInfo ? info) async {
157
+ Response <dynamic >? res = await dio.get (WEBVPN_ID_REQUEST_URL ,
158
+ options: DioUtils .NON_REDIRECT_OPTION_WITH_FORM_TYPE );
159
+ if (res.statusCode == 302 ) {
160
+ await DioUtils .processRedirect (dio, res);
161
+ res = await UISLoginTool .loginUIS (dio, WEBVPN_UIS_LOGIN_URL , jar, info);
162
+ if (res == null ) {
163
+ throw WebvpnRequestException ("Failed to login to UIS" );
164
+ }
165
+ }
166
+ final ticket = _retrieveTicket (res);
167
+
168
+ Map <String , dynamic > queryParams = {
169
+ 'cas_login' : 'true' ,
170
+ 'ticket' : ticket,
171
+ };
172
+
173
+ final response = await dio.get (WEBVPN_LOGIN_URL ,
174
+ queryParameters: queryParams,
175
+ options: DioUtils .NON_REDIRECT_OPTION_WITH_FORM_TYPE );
176
+ await DioUtils .processRedirect (dio, response);
177
+ }
178
+
179
+ static String ? _retrieveTicket (Response <dynamic > response) {
180
+ // Check if the URL host matches the expected value
181
+ if (response.realUri.host != "id.fudan.edu.cn" ) {
182
+ return null ;
183
+ }
184
+
185
+ BeautifulSoup soup = BeautifulSoup (response.data! );
186
+
187
+ final element = soup.find ('' , selector: '#ticket' );
188
+ return element? .attributes['value' ];
189
+ }
190
+
137
191
/// Check if we are able to connect to the service directly (without WebVPN).
138
192
/// This method uses a low-timeout dio to reduce wait time.
139
193
static Future <bool > tryDirect <T >() async {
0 commit comments