aboutsummaryrefslogtreecommitdiff
path: root/central/src/main/java/moe/yuuta/dn42peering/asn/ASNServiceImpl.java
blob: 4a05588880e04ee03fba22fe935636132fa30517 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package moe.yuuta.dn42peering.asn;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.mysqlclient.MySQLPool;
import io.vertx.sqlclient.*;
import io.vertx.sqlclient.templates.SqlTemplate;
import moe.yuuta.dn42peering.utils.PasswordAuthentication;
import moe.yuuta.dn42peering.whois.IWhoisService;
import moe.yuuta.dn42peering.whois.WhoisObject;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class ASNServiceImpl implements IASNService {
    private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());

    private final Vertx vertx;
    private final MySQLPool pool;

    ASNServiceImpl(@Nonnull Vertx vertx, @Nonnull MySQLPool pool) {
        this.vertx = vertx;
        this.pool = pool;
    }

    @Nonnull
    @Override
    public IASNService exists(@Nonnull String asn, boolean requireActivationStatus, boolean activated,
                              @Nonnull Handler<AsyncResult<Boolean>> handler) {
        final PreparedQuery<RowSet<Row>> preparedQuery =
                !requireActivationStatus ? pool.preparedQuery("SELECT COUNT(asn) FROM asn WHERE asn = ?")
                                : pool.preparedQuery("SELECT COUNT(asn) FROM asn WHERE asn = ? AND activated = ?");
        preparedQuery.execute(!requireActivationStatus ? Tuple.of(asn) : Tuple.of(asn, activated))
                .compose(rows -> {
                    int count = rows.iterator().next().getInteger(0);
                    return Future.succeededFuture(count > 0);
                })
                .onComplete(handler);
        return this;
    }

    @Nonnull
    @Override
    public IASNService markAsActivated(@Nonnull String asn, @Nonnull Handler<AsyncResult<Void>> handler) {
        pool.preparedQuery("UPDATE asn SET activated = 1 WHERE asn = ? AND activated = 0")
                .execute(Tuple.of(asn))
                .<Void>compose(rows -> {
                    return Future.succeededFuture(null);
                })
                .onComplete(handler);
        return this;
    }

    @Nonnull
    @Override
    public IASNService changePassword(@Nonnull String asn, @Nonnull String newPassword, @Nonnull Handler<AsyncResult<Void>> handler) {
        final Map<String, Object> params = new HashMap<>(2);
        params.put("asn", asn);
        params.put("password_hash", new PasswordAuthentication()
                .hash(newPassword.toCharArray()));
        Future.<SqlResult<Void>>future(f -> SqlTemplate
                .forUpdate(pool, "UPDATE asn SET password_hash = #{password_hash} WHERE asn = #{asn}")
                .execute(params, f))
                .<Void>compose(voidSqlResult -> Future.succeededFuture(null))
                .onComplete(handler);
        return this;
    }

    @Nonnull
    @Override
    public IASNService auth(@Nonnull String asn, @Nonnull String password, @Nonnull Handler<AsyncResult<Boolean>> handler) {
        Future.<RowSet<ASN>>future(f -> SqlTemplate
                .forQuery(pool, "SELECT password_hash FROM asn WHERE asn = #{asn}")
                .mapTo(ASNRowMapper.INSTANCE)
                .mapFrom(ASNParametersMapper.INSTANCE)
                .execute(new ASN(asn, "", false, new Date()), f))
                .compose(rows -> {
                    // No such user
                    if(!rows.iterator().hasNext()) {
                        return Future.succeededFuture(false);
                    }
                    final ASN record = rows.iterator().next();
                    return Future.succeededFuture(record.auth(password));
                })
                .onComplete(handler);
        return this;
    }

    @Nonnull
    @Override
    public IASNService delete(@Nonnull String asn, @Nonnull Handler<AsyncResult<Void>> handler) {
        Future.<SqlResult<Void>>future(f -> SqlTemplate.forUpdate(pool, "DELETE FROM asn WHERE asn = #{asn}")
        .execute(Collections.singletonMap("asn", asn), f))
                .<Void>compose(voidSqlResult -> Future.succeededFuture())
                .onComplete(handler);
        return this;
    }

    @Nonnull
    @Override
    public IASNService lookupEmails(@Nonnull WhoisObject asn, @Nonnull Handler<AsyncResult<List<String>>> handler) {
        if (!asn.containsKey("tech-c") || asn.get("tech-c").isEmpty()) {
            handler.handle(Future.succeededFuture(null));
            return this;
        }
        Future.<WhoisObject>future(f -> IWhoisService.createProxy(vertx, IWhoisService.ADDRESS)
                .query(asn.get("tech-c").get(0) /* tech-c is only permitted to appear once */, f))
                .compose(techLookup -> {
                    if(techLookup == null) {
                        return Future.succeededFuture(Collections.<String>emptyList());
                    } else {
                        final List<String> verifyMethodList = new ArrayList<>(3);
                        verifyMethodList.addAll(Stream.of(techLookup.getOrDefault("contact", Collections.emptyList()))
                                .flatMap(Collection::stream)
                                .map(contact -> {
                                    final Matcher m =
                                            Pattern.compile("[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+")
                                                    .matcher(contact);
                                    final List<String> verifiers = new ArrayList<>(1);
                                    while (m.find()) {
                                        verifiers.add(m.group());
                                    }
                                    return verifiers;
                                })
                                .flatMap(List::stream)
                                .collect(Collectors.toList()));
                        return Future.succeededFuture(verifyMethodList);
                    }
                })
                .onComplete(handler);
        return this;
    }

    @Nonnull
    @Override
    public IASNService registerOrChangePassword(@Nonnull String asn, @Nonnull String newPassword, @Nonnull Handler<AsyncResult<Void>> handler) {
        // TODO: No activated check here.
        Future.<RowSet<Row>>future(f -> pool.preparedQuery("REPLACE INTO asn (asn, password_hash) VALUES(?, ?)")
                .execute(Tuple.of(asn, new PasswordAuthentication().hash(newPassword.toCharArray())), f))
                .<Void>compose(res -> Future.succeededFuture(null))
                .onComplete(handler);
        return this;
    }
}