Skip to content

Commit 46763a7

Browse files
author
dapeng
committed
hbase kerberos功能
1 parent 1f48d45 commit 46763a7

File tree

5 files changed

+161
-112
lines changed

5 files changed

+161
-112
lines changed

hbase/hbase-side/hbase-all-side/src/main/java/com/dtstack/flink/sql/side/hbase/HbaseAllReqRow.java

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -179,22 +179,14 @@ private void loadData(Map<String, Map<String, Object>> tmpCache) throws SQLExcep
179179
boolean openKerberos = hbaseSideTableInfo.isKerberosAuthEnable();
180180
int loadDataCount = 0;
181181
try {
182-
conf = HBaseConfiguration.create();
183182
if (openKerberos) {
183+
conf = HbaseConfigUtils.getHadoopConfiguration(hbaseSideTableInfo.getHbaseConfig());
184184
conf.set(HbaseConfigUtils.KEY_HBASE_ZOOKEEPER_QUORUM, hbaseSideTableInfo.getHost());
185-
conf.set(HbaseConfigUtils.KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM_SYNC, hbaseSideTableInfo.getParent());
186-
187-
fillSyncKerberosConfig(conf,hbaseSideTableInfo);
188-
LOG.info("hbase.security.authentication:{}", conf.get("hbase.security.authentication"));
189-
LOG.info("hbase.security.authorization:{}", conf.get("hbase.security.authorization"));
190-
LOG.info("hbase.master.keytab.file:{}", conf.get("hbase.master.keytab.file"));
191-
LOG.info("hbase.master.kerberos.principal:{}", conf.get("hbase.master.kerberos.principal"));
192-
LOG.info("hbase.regionserver.keytab.file:{}", conf.get("hbase.regionserver.keytab.file"));
193-
LOG.info("hbase.regionserver.kerberos.principal:{}", conf.get("hbase.regionserver.kerberos.principal"));
194-
195-
UserGroupInformation userGroupInformation = HbaseConfigUtils.loginAndReturnUGI(conf, hbaseSideTableInfo.getRegionserverPrincipal(),
196-
hbaseSideTableInfo.getRegionserverKeytabFile());
185+
conf.set(HbaseConfigUtils.KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM, hbaseSideTableInfo.getParent());
186+
String principal = HbaseConfigUtils.getPrincipal(hbaseSideTableInfo.getHbaseConfig());
187+
String keytab = HbaseConfigUtils.getKeytab(hbaseSideTableInfo.getHbaseConfig());
197188

189+
UserGroupInformation userGroupInformation = HbaseConfigUtils.loginAndReturnUGI(conf, principal, keytab);
198190
Configuration finalConf = conf;
199191
conn = userGroupInformation.doAs(new PrivilegedAction<Connection>() {
200192
@Override
@@ -209,8 +201,9 @@ public Connection run() {
209201
});
210202

211203
} else {
204+
conf = HbaseConfigUtils.getConfig(hbaseSideTableInfo.getHbaseConfig());
212205
conf.set(HbaseConfigUtils.KEY_HBASE_ZOOKEEPER_QUORUM, hbaseSideTableInfo.getHost());
213-
conf.set(HbaseConfigUtils.KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM_SYNC, hbaseSideTableInfo.getParent());
206+
conf.set(HbaseConfigUtils.KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM, hbaseSideTableInfo.getParent());
214207
conn = ConnectionFactory.createConnection(conf);
215208
}
216209

@@ -253,33 +246,4 @@ public Connection run() {
253246
}
254247
}
255248

256-
private void fillSyncKerberosConfig(Configuration config, HbaseSideTableInfo hbaseSideTableInfo) throws IOException {
257-
String regionserverKeytabFile = hbaseSideTableInfo.getRegionserverKeytabFile();
258-
if (StringUtils.isEmpty(regionserverKeytabFile)) {
259-
throw new IllegalArgumentException("Must provide regionserverKeytabFile when authentication is Kerberos");
260-
}
261-
String regionserverKeytabFilePath = System.getProperty("user.dir") + File.separator + regionserverKeytabFile;
262-
LOG.info("regionserverKeytabFilePath:{}", regionserverKeytabFilePath);
263-
config.set(HbaseConfigUtils.KEY_HBASE_MASTER_KEYTAB_FILE, regionserverKeytabFilePath);
264-
config.set(HbaseConfigUtils.KEY_HBASE_REGIONSERVER_KEYTAB_FILE, regionserverKeytabFilePath);
265-
266-
String regionserverPrincipal = hbaseSideTableInfo.getRegionserverPrincipal();
267-
if (StringUtils.isEmpty(regionserverPrincipal)) {
268-
throw new IllegalArgumentException("Must provide regionserverPrincipal when authentication is Kerberos");
269-
}
270-
config.set(HbaseConfigUtils.KEY_HBASE_MASTER_KERBEROS_PRINCIPAL, regionserverPrincipal);
271-
config.set(HbaseConfigUtils.KEY_HBASE_REGIONSERVER_KERBEROS_PRINCIPAL, regionserverPrincipal);
272-
config.set(HbaseConfigUtils.KEY_HBASE_SECURITY_AUTHORIZATION, "true");
273-
config.set(HbaseConfigUtils.KEY_HBASE_SECURITY_AUTHENTICATION, "kerberos");
274-
275-
if (!StringUtils.isEmpty(hbaseSideTableInfo.getZookeeperSaslClient())) {
276-
System.setProperty(HbaseConfigUtils.KEY_ZOOKEEPER_SASL_CLIENT, hbaseSideTableInfo.getZookeeperSaslClient());
277-
}
278-
279-
if (!StringUtils.isEmpty(hbaseSideTableInfo.getSecurityKrb5Conf())) {
280-
String krb5ConfPath = System.getProperty("user.dir") + File.separator + hbaseSideTableInfo.getSecurityKrb5Conf();
281-
LOG.info("krb5ConfPath:{}", krb5ConfPath);
282-
System.setProperty(HbaseConfigUtils.KEY_JAVA_SECURITY_KRB5_CONF, krb5ConfPath);
283-
}
284-
}
285249
}

hbase/hbase-side/hbase-async-side/src/main/java/com/dtstack/flink/sql/side/hbase/HbaseAsyncReqRow.java

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -187,33 +187,6 @@ public void close() throws Exception {
187187
hBaseClient.shutdown();
188188
}
189189

190-
private void fillAsyncKerberosConfig(Config config, HbaseSideTableInfo hbaseSideTableInfo) throws IOException {
191-
AuthUtil.JAASConfig jaasConfig = HbaseConfigUtils.buildJaasConfig(hbaseSideTableInfo);
192-
LOG.info("jaasConfig file:\n {}", jaasConfig.toString());
193-
String jaasFilePath = AuthUtil.creatJaasFile("JAAS", ".conf", jaasConfig);
194-
config.overrideConfig(HbaseConfigUtils.KEY_JAVA_SECURITY_AUTH_LOGIN_CONF, jaasFilePath);
195-
config.overrideConfig(HbaseConfigUtils.KEY_HBASE_SECURITY_AUTH_ENABLE, "true");
196-
config.overrideConfig(HbaseConfigUtils.KEY_HBASE_SASL_CLIENTCONFIG, "Client");
197-
config.overrideConfig(HbaseConfigUtils.KEY_HBASE_SECURITY_AUTHENTICATION, "kerberos");
198-
199-
String regionserverPrincipal = hbaseSideTableInfo.getRegionserverPrincipal();
200-
if (StringUtils.isEmpty(regionserverPrincipal)) {
201-
throw new IllegalArgumentException("Must provide regionserverPrincipal when authentication is Kerberos");
202-
}
203-
config.overrideConfig(HbaseConfigUtils.KEY_HBASE_KERBEROS_REGIONSERVER_PRINCIPAL, regionserverPrincipal);
204-
205-
if (!StringUtils.isEmpty(hbaseSideTableInfo.getZookeeperSaslClient())) {
206-
System.setProperty(HbaseConfigUtils.KEY_ZOOKEEPER_SASL_CLIENT, hbaseSideTableInfo.getZookeeperSaslClient());
207-
}
208-
209-
if (!StringUtils.isEmpty(hbaseSideTableInfo.getSecurityKrb5Conf())) {
210-
String krb5ConfPath = System.getProperty("user.dir") + File.separator + hbaseSideTableInfo.getSecurityKrb5Conf();
211-
LOG.info("krb5ConfPath:{}", krb5ConfPath);
212-
System.setProperty(HbaseConfigUtils.KEY_JAVA_SECURITY_KRB5_CONF, krb5ConfPath);
213-
}
214-
}
215-
216-
217190
class CheckResult{
218191

219192
private boolean connect;

hbase/hbase-side/hbase-side-core/src/main/java/com/dtstack/flink/sql/side/hbase/table/HbaseSideParser.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,6 @@ public class HbaseSideParser extends AbstractSideTableParser {
5353

5454
public static final String CACHE = "cache";
5555

56-
public static final String KERBEROS_AUTH_ENABLE_KEY = "kerberosAuthEnable";
57-
public static final String REGIONSERVER_KEYTAB_FILE_KEY = "regionserverKeytabFile";
58-
public static final String REGIONSERVER_PRINCIPAL_KEY = "regionserverPrincipal";
59-
public static final String JAAS_PRINCIPAL_KEY = "jaasPrincipal";
60-
public static final String SECURITY_KRB5_CONF_KEY = "securityKrb5Conf";
61-
public static final String ZOOKEEPER_SASL_CLINT_KEY = "zookeeperSaslClient";
62-
6356
public HbaseSideParser() {
6457
addParserHandler(FIELD_KEY, FIELD_PATTERN, this::dealField);
6558
}
@@ -76,13 +69,10 @@ public AbstractTableInfo getTableInfo(String tableName, String fieldsInfo, Map<S
7669
hbaseTableInfo.setParent((String)props.get(ZOOKEEPER_PARENT.toLowerCase()));
7770
hbaseTableInfo.setPreRowKey(MathUtil.getBoolean(props.get(PRE_ROW_KEY.toLowerCase()), false));
7871
hbaseTableInfo.setCacheType((String) props.get(CACHE));
79-
80-
hbaseTableInfo.setKerberosAuthEnable(MathUtil.getBoolean(props.get(KERBEROS_AUTH_ENABLE_KEY.toLowerCase()), false));
81-
hbaseTableInfo.setRegionserverKeytabFile((String) props.get(REGIONSERVER_KEYTAB_FILE_KEY.toLowerCase()));
82-
hbaseTableInfo.setRegionserverPrincipal((String) props.get(REGIONSERVER_PRINCIPAL_KEY.toLowerCase()));
83-
hbaseTableInfo.setJaasPrincipal((String) props.get(JAAS_PRINCIPAL_KEY.toLowerCase()));
84-
hbaseTableInfo.setSecurityKrb5Conf((String) props.get(SECURITY_KRB5_CONF_KEY.toLowerCase()));
85-
hbaseTableInfo.setZookeeperSaslClient((String) props.get(ZOOKEEPER_SASL_CLINT_KEY.toLowerCase()));
72+
props.entrySet().stream()
73+
.filter(entity -> entity.getKey().contains("."))
74+
.map(entity -> hbaseTableInfo.getHbaseConfig().put(entity.getKey(), String.valueOf(entity.getValue())))
75+
.count();
8676
return hbaseTableInfo;
8777
}
8878

hbase/hbase-side/hbase-side-core/src/main/java/com/dtstack/flink/sql/side/hbase/table/HbaseSideTableInfo.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public class HbaseSideTableInfo extends AbstractSideTableInfo {
5050

5151
private Map<String, String> columnNameFamily;
5252

53+
private Map<String, Object> hbaseConfig = Maps.newHashMap();
54+
5355
private String tableName;
5456

5557
private boolean kerberosAuthEnable;
@@ -208,6 +210,15 @@ public void setZookeeperSaslClient(String zookeeperSaslClient) {
208210
this.zookeeperSaslClient = zookeeperSaslClient;
209211
}
210212

213+
public Map<String, Object> getHbaseConfig() {
214+
return hbaseConfig;
215+
}
216+
217+
public void setHbaseConfig(Map<String, Object> hbaseConfig) {
218+
this.hbaseConfig = hbaseConfig;
219+
}
220+
221+
211222
@Override
212223
public void finish(){
213224
super.finish();

hbase/hbase-side/hbase-side-core/src/main/java/com/dtstack/flink/sql/side/hbase/utils/HbaseConfigUtils.java

Lines changed: 139 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
package com.dtstack.flink.sql.side.hbase.utils;
2020

21-
import com.dtstack.flink.sql.side.hbase.table.HbaseSideTableInfo;
22-
import com.dtstack.flink.sql.util.AuthUtil;
2321
import org.apache.commons.collections.MapUtils;
2422
import org.apache.commons.lang3.StringUtils;
2523
import org.apache.hadoop.conf.Configuration;
@@ -33,7 +31,6 @@
3331
import java.io.FileWriter;
3432
import java.io.IOException;
3533
import java.util.Arrays;
36-
import java.util.HashMap;
3734
import java.util.List;
3835
import java.util.Map;
3936
import java.util.UUID;
@@ -50,42 +47,157 @@ public class HbaseConfigUtils {
5047

5148
private static final Logger LOG = LoggerFactory.getLogger(HbaseConfigUtils.class);
5249
// sync side kerberos
53-
public final static String KEY_HBASE_SECURITY_AUTHENTICATION = "hbase.security.authentication";
54-
public final static String KEY_HBASE_SECURITY_AUTHORIZATION = "hbase.security.authorization";
55-
public final static String KEY_HBASE_MASTER_KEYTAB_FILE = "hbase.master.keytab.file";
56-
public final static String KEY_HBASE_MASTER_KERBEROS_PRINCIPAL = "hbase.master.kerberos.principal";
57-
public final static String KEY_HBASE_REGIONSERVER_KEYTAB_FILE = "hbase.regionserver.keytab.file";
58-
public final static String KEY_HBASE_REGIONSERVER_KERBEROS_PRINCIPAL = "hbase.regionserver.kerberos.principal";
50+
private final static String AUTHENTICATION_TYPE = "Kerberos";
51+
private final static String KEY_HBASE_SECURITY_AUTHENTICATION = "hbase.security.authentication";
52+
private final static String KEY_HBASE_SECURITY_AUTHORIZATION = "hbase.security.authorization";
53+
private final static String KEY_HBASE_MASTER_KERBEROS_PRINCIPAL = "hbase.master.kerberos.principal";
54+
private final static String KEY_HBASE_MASTER_KEYTAB_FILE = "hbase.master.keytab.file";
55+
private final static String KEY_HBASE_REGIONSERVER_KEYTAB_FILE = "hbase.regionserver.keytab.file";
56+
private final static String KEY_HBASE_REGIONSERVER_KERBEROS_PRINCIPAL = "hbase.regionserver.kerberos.principal";
5957

6058
// async side kerberos
61-
public final static String KEY_HBASE_SECURITY_AUTH_ENABLE = "hbase.security.auth.enable";
62-
public final static String KEY_HBASE_SASL_CLIENTCONFIG = "hbase.sasl.clientconfig";
63-
public final static String KEY_HBASE_KERBEROS_REGIONSERVER_PRINCIPAL = "hbase.kerberos.regionserver.principal";
59+
private final static String KEY_HBASE_SECURITY_AUTH_ENABLE = "hbase.security.auth.enable";
60+
private final static String KEY_HBASE_SASL_CLIENTCONFIG = "hbase.sasl.clientconfig";
61+
private final static String KEY_HBASE_KERBEROS_REGIONSERVER_PRINCIPAL = "hbase.kerberos.regionserver.principal";
62+
private static final String KEY_KEY_TAB = "hbase.keytab";
63+
private static final String KEY_PRINCIPAL = "hbase.principal";
6464

6565
public final static String KEY_HBASE_ZOOKEEPER_QUORUM = "hbase.zookeeper.quorum";
66-
public final static String KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM_SYNC = "zookeeper.znode.parent";
67-
public final static String KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM_ASYNC = "hbase.zookeeper.znode.parent";
66+
public final static String KEY_HBASE_ZOOKEEPER_ZNODE_QUORUM = "hbase.zookeeper.znode.parent";
6867

6968

70-
public static final String KEY_JAVA_SECURITY_KRB5_CONF = "java.security.krb5.conf";
71-
public static final String KEY_ZOOKEEPER_SASL_CLIENT = "zookeeper.sasl.client";
72-
69+
private static final String KEY_JAVA_SECURITY_KRB5_CONF = "java.security.krb5.conf";
7370
public static final String KEY_JAVA_SECURITY_AUTH_LOGIN_CONF = "java.security.auth.login.config";
7471

7572

76-
public static AuthUtil.JAASConfig buildJaasConfig(HbaseSideTableInfo hbaseSideTableInfo) {
77-
String keytabPath = System.getProperty("user.dir") + File.separator + hbaseSideTableInfo.getRegionserverKeytabFile();
78-
Map<String, String> loginModuleOptions = new HashMap<>();
79-
loginModuleOptions.put("useKeyTab", "true");
80-
loginModuleOptions.put("useTicketCache", "false");
81-
loginModuleOptions.put("keyTab", "\"" + keytabPath + "\"");
82-
loginModuleOptions.put("principal", "\"" + hbaseSideTableInfo.getJaasPrincipal() + "\"");
83-
return AuthUtil.JAASConfig.builder().setEntryName("Client")
84-
.setLoginModule("com.sun.security.auth.module.Krb5LoginModule")
85-
.setLoginModuleFlag("required").setLoginModuleOptions(loginModuleOptions).build();
73+
private static final String SP = File.separator;
74+
private static final String KEY_KRB5_CONF = "krb5.conf";
75+
76+
77+
private static List<String> KEYS_KERBEROS_REQUIRED = Arrays.asList(
78+
KEY_HBASE_SECURITY_AUTHENTICATION,
79+
KEY_HBASE_MASTER_KERBEROS_PRINCIPAL,
80+
KEY_HBASE_MASTER_KEYTAB_FILE,
81+
KEY_HBASE_REGIONSERVER_KEYTAB_FILE,
82+
KEY_HBASE_REGIONSERVER_KERBEROS_PRINCIPAL
83+
);
84+
85+
private static List<String> ASYNC_KEYS_KERBEROS_REQUIRED = Arrays.asList(
86+
KEY_HBASE_SECURITY_AUTH_ENABLE,
87+
KEY_HBASE_SASL_CLIENTCONFIG,
88+
KEY_HBASE_KERBEROS_REGIONSERVER_PRINCIPAL,
89+
KEY_HBASE_SECURITY_AUTHENTICATION,
90+
KEY_KEY_TAB);
91+
92+
93+
public static Configuration getConfig(Map<String, Object> hbaseConfigMap) {
94+
Configuration hConfiguration = HBaseConfiguration.create();
95+
96+
for (Map.Entry<String, Object> entry : hbaseConfigMap.entrySet()) {
97+
if (entry.getValue() != null && !(entry.getValue() instanceof Map)) {
98+
hConfiguration.set(entry.getKey(), entry.getValue().toString());
99+
}
100+
}
101+
return hConfiguration;
102+
}
103+
104+
public static boolean openKerberos(Map<String, Object> hbaseConfigMap) {
105+
if (!MapUtils.getBooleanValue(hbaseConfigMap, KEY_HBASE_SECURITY_AUTHORIZATION)) {
106+
return false;
107+
}
108+
return AUTHENTICATION_TYPE.equalsIgnoreCase(MapUtils.getString(hbaseConfigMap, KEY_HBASE_SECURITY_AUTHENTICATION));
109+
}
110+
111+
public static boolean asyncOpenKerberos(Map<String, Object> hbaseConfigMap) {
112+
if (!MapUtils.getBooleanValue(hbaseConfigMap, KEY_HBASE_SECURITY_AUTH_ENABLE)) {
113+
return false;
114+
}
115+
return AUTHENTICATION_TYPE.equalsIgnoreCase(MapUtils.getString(hbaseConfigMap, KEY_HBASE_SECURITY_AUTHENTICATION));
86116
}
87117

88118

119+
120+
121+
public static Configuration getHadoopConfiguration(Map<String, Object> hbaseConfigMap) {
122+
for (String key : KEYS_KERBEROS_REQUIRED) {
123+
if (StringUtils.isEmpty(MapUtils.getString(hbaseConfigMap, key))) {
124+
throw new IllegalArgumentException(String.format("Must provide [%s] when authentication is Kerberos", key));
125+
}
126+
}
127+
loadKrb5Conf(hbaseConfigMap);
128+
129+
Configuration conf = new Configuration();
130+
if (hbaseConfigMap == null) {
131+
return conf;
132+
}
133+
134+
hbaseConfigMap.forEach((key, val) -> {
135+
if (val != null) {
136+
conf.set(key, val.toString());
137+
}
138+
});
139+
140+
return conf;
141+
}
142+
143+
public static String getPrincipal(Map<String, Object> hbaseConfigMap) {
144+
String principal = MapUtils.getString(hbaseConfigMap, KEY_HBASE_MASTER_KERBEROS_PRINCIPAL);
145+
if (StringUtils.isNotEmpty(principal)) {
146+
return principal;
147+
}
148+
149+
throw new IllegalArgumentException("");
150+
}
151+
152+
public static String getKeytab(Map<String, Object> hbaseConfigMap) {
153+
String keytab = MapUtils.getString(hbaseConfigMap, KEY_HBASE_MASTER_KEYTAB_FILE);
154+
if (StringUtils.isNotEmpty(keytab)) {
155+
return keytab;
156+
}
157+
158+
throw new IllegalArgumentException("");
159+
}
160+
161+
public static void loadKrb5Conf(Map<String, Object> kerberosConfig) {
162+
String krb5FilePath = MapUtils.getString(kerberosConfig, KEY_JAVA_SECURITY_KRB5_CONF);
163+
if (!org.apache.commons.lang.StringUtils.isEmpty(krb5FilePath)) {
164+
System.setProperty(KEY_JAVA_SECURITY_KRB5_CONF, krb5FilePath);;
165+
}
166+
}
167+
168+
public static String creatJassFile(String configStr) throws IOException {
169+
String fileName = System.getProperty("user.dir");
170+
File krbConf = new File(fileName);
171+
File temp = File.createTempFile("JAAS", ".conf", krbConf);
172+
temp.deleteOnExit();
173+
BufferedWriter out = new BufferedWriter(new FileWriter(temp, false));
174+
out.write(configStr + "\n");
175+
out.close();
176+
return temp.getAbsolutePath();
177+
}
178+
179+
public static String buildJaasStr(Map<String, Object> kerberosConfig) {
180+
for (String key : ASYNC_KEYS_KERBEROS_REQUIRED) {
181+
if (StringUtils.isEmpty(MapUtils.getString(kerberosConfig, key))) {
182+
throw new IllegalArgumentException(String.format("Must provide [%s] when authentication is Kerberos", key));
183+
}
184+
}
185+
186+
String keyTab = MapUtils.getString(kerberosConfig, KEY_KEY_TAB);
187+
String principal = MapUtils.getString(kerberosConfig, KEY_PRINCIPAL);
188+
189+
StringBuilder jaasSB = new StringBuilder("Client {\n" +
190+
" com.sun.security.auth.module.Krb5LoginModule required\n" +
191+
" useKeyTab=true\n" +
192+
" useTicketCache=false\n");
193+
jaasSB.append(" keyTab=\"").append(keyTab).append("\"").append("\n");
194+
jaasSB.append(" principal=\"").append(principal).append("\"").append(";\n");
195+
jaasSB.append("};");
196+
return jaasSB.toString();
197+
}
198+
199+
200+
89201
public static UserGroupInformation loginAndReturnUGI(Configuration conf, String principal, String keytab) throws IOException {
90202
if (conf == null) {
91203
throw new IllegalArgumentException("kerberos conf can not be null");
@@ -104,5 +216,4 @@ public static UserGroupInformation loginAndReturnUGI(Configuration conf, String
104216

105217
return UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, keytab);
106218
}
107-
108219
}

0 commit comments

Comments
 (0)