getFilenames() {
123 | return filenames;
124 | }
125 |
126 | public String toString() {
127 | String s = "";
128 | s += "ReturnValueWrapper. isValidSession=" + isSessionValid
129 | + ", objectID=" + objectID + ", filenames=" + filenames;
130 | return s;
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/wowza-ticket-checker-module/src/main/java/dk/statsbiblioteket/medieplatform/wowza/plugin/TicketChecker.java:
--------------------------------------------------------------------------------
1 | package dk.statsbiblioteket.medieplatform.wowza.plugin;
2 |
3 | import com.wowza.wms.client.IClient;
4 | import com.wowza.wms.httpstreamer.model.IHTTPStreamerSession;
5 | import com.wowza.wms.logging.WMSLogger;
6 | import com.wowza.wms.logging.WMSLoggerFactory;
7 | import com.wowza.wms.stream.IMediaStream;
8 |
9 | import dk.statsbiblioteket.medieplatform.ticketsystem.Ticket;
10 | import dk.statsbiblioteket.medieplatform.wowza.plugin.ticket.TicketToolInterface;
11 | import dk.statsbiblioteket.medieplatform.wowza.plugin.utilities.IllegallyFormattedQueryStringException;
12 | import dk.statsbiblioteket.medieplatform.wowza.plugin.utilities.StringAndTextUtil;
13 |
14 | /**
15 | * This class is used to validate the ticket
16 | */
17 | public class TicketChecker {
18 | private final WMSLogger logger;
19 | private final String presentationType;
20 | private final TicketToolInterface ticketTool;
21 |
22 | public TicketChecker(String presentationType, TicketToolInterface ticketTool) {
23 | super();
24 | this.presentationType = presentationType;
25 | this.logger = WMSLoggerFactory.getLogger(this.getClass());
26 | this.ticketTool = ticketTool;
27 | }
28 |
29 | /**
30 | * Check if a stream is allowed to play
31 | *
32 | * @param stream the stream to check
33 | * @param client the client trying to play the stream
34 | * @return true if allowed, false otherwise.
35 | * @see #checkTicket(java.lang.String, java.lang.String, java.lang.String)
36 | */
37 | public boolean checkTicket(IMediaStream stream, IClient client) {
38 | if (client == null) {
39 | logger.debug("No client, returning ", stream);
40 | return false;
41 | }
42 | return checkTicket(stream.getName(), client.getQueryStr(), getClientIp(client));
43 | }
44 |
45 | /**
46 | * Check if a stream is allowed to play.
47 | *
48 | * Extracts streamname, query string and client ip from the http session
49 | *
50 | * @param httpSession The http session
51 | * @return true if allowed, false otherwise.
52 | * @see #checkTicket(java.lang.String, java.lang.String, java.lang.String)
53 | */
54 | public boolean checkTicket(IHTTPStreamerSession httpSession) {
55 | return checkTicket(httpSession.getStreamName(), httpSession.getQueryStr(), getClientIp(httpSession));
56 | }
57 |
58 | private boolean checkTicket(String name, String query, String ip) {
59 | logger.trace(
60 | "checkTicket(String name=" + name
61 | + ", String query=" + query + ")");
62 | try {
63 | Ticket streamingTicket = StringAndTextUtil.getTicket(query, ticketTool);
64 | logger.debug("Ticket received: " + (streamingTicket != null ? streamingTicket.getId() : "null"));
65 | if (
66 | streamingTicket != null &&
67 | isClientAllowed(streamingTicket, ip) &&
68 | ticketForThisPresentationType(streamingTicket) &&
69 | doesTicketAllowThisStream(name, streamingTicket)
70 | ) {
71 | logger.info(
72 | "checkTicket(String name=" + name
73 | + ", String query=" + query + ") successful.");
74 | return true;
75 | } else {
76 | logger.info("Client not allowed to get content streamed for String name=" + name
77 | + ", String query=" + query
78 | + ")");
79 | return false;
80 | }
81 | } catch (IllegallyFormattedQueryStringException e) {
82 | logger.warn("Illegally formatted query string [" + query + "].");
83 | return false;
84 | }
85 | }
86 |
87 | private boolean ticketForThisPresentationType(Ticket streamingTicket) {
88 | return streamingTicket.getType().equals(presentationType);
89 | }
90 |
91 | private boolean doesTicketAllowThisStream(String name, Ticket streamingTicket) {
92 | name = clean(name);
93 | for (String resource : streamingTicket.getResources()) {
94 | if (resource.contains(name)){
95 | return true;
96 | }
97 | }
98 | return false;
99 | }
100 |
101 | /**
102 | * This method checks if the ticket is given to the same IP address as the client
103 | * @param streamingTicket the ticket
104 | * @param ip ip of client
105 | * @return true if the ip is the same for the ticket and the user
106 | */
107 | private boolean isClientAllowed(Ticket streamingTicket, String ip) {
108 |
109 | boolean isAllowed = (ip != null) && (ip.equals(streamingTicket.getIpAddress()));
110 | logger.debug("isClientAllowed - ipOfClient: " + ip + ", streamingTicket.getIpAddress(): "
111 | + streamingTicket.getIpAddress() + ", isAllowed: " + isAllowed);
112 | return isAllowed;
113 | }
114 |
115 | private String clean(String name) {
116 | if (name.contains(".")){
117 | name = name.substring(0, name.indexOf("."));
118 | }
119 | if (name.contains(":")) {
120 | name = name.substring(name.lastIndexOf(':') + 1);
121 | }
122 |
123 | return name;
124 | }
125 |
126 | private String getClientIp(IClient client) {
127 | String IP = (client.getForwardedIP()) != null ? client.getForwardedIP() : client.getIp();
128 | return IP;
129 | }
130 |
131 | private String getClientIp(IHTTPStreamerSession httpSession) {
132 | String IP = (httpSession.getForwardedIP()) != null ? httpSession.getForwardedIP() : httpSession.getIpAddress();
133 | return IP;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/wowza-statistics-module/src/main/java/dk/statsbiblioteket/medieplatform/wowza/plugin/streamingstatistics/StreamingEventLogger.java:
--------------------------------------------------------------------------------
1 | package dk.statsbiblioteket.medieplatform.wowza.plugin.streamingstatistics;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.nio.charset.StandardCharsets;
7 | import java.nio.file.Files;
8 | import java.nio.file.StandardOpenOption;
9 | import java.text.SimpleDateFormat;
10 | import java.time.LocalDate;
11 | import java.time.LocalDateTime;
12 | import java.time.LocalTime;
13 | import java.time.ZoneId;
14 | import java.util.Date;
15 | import java.util.Locale;
16 |
17 | import com.wowza.wms.logging.WMSLogger;
18 |
19 | import dk.statsbiblioteket.medieplatform.ticketsystem.Ticket;
20 | import dk.statsbiblioteket.medieplatform.wowza.plugin.streamingstatistics.StreamingStatLogEntry.Event;
21 | import dk.statsbiblioteket.medieplatform.wowza.plugin.ticket.TicketToolInterface;
22 | import dk.statsbiblioteket.medieplatform.wowza.plugin.utilities.IllegallyFormattedQueryStringException;
23 | import dk.statsbiblioteket.medieplatform.wowza.plugin.utilities.StringAndTextUtil;
24 |
25 | public class StreamingEventLogger {
26 | private static final String DATE_PATTERN = "yyyy-MM-dd";
27 | public static final String FILENAME_PREFIX = "StreamingStat-";
28 | private String statLogFileHomeDir;
29 | private BufferedWriter statLogWriter;
30 | private Date dateForNewLogFile;
31 |
32 | private WMSLogger logger;
33 | private TicketToolInterface ticketTool;
34 | private String newlineString;
35 |
36 | public StreamingEventLogger(TicketToolInterface ticketTool, WMSLogger logger, String statLogFileHomeDir) {
37 | super();
38 | this.logger = logger;
39 | this.ticketTool = ticketTool;
40 | this.statLogFileHomeDir = statLogFileHomeDir;
41 | this.statLogWriter = null;
42 | logger.info("Statistics logger " + this.getClass().getName() + " has been created, logging files to '"
43 | + statLogFileHomeDir + "'.");
44 | this.dateForNewLogFile = new Date();
45 | this.newlineString = System.getProperty("line.separator");
46 | }
47 |
48 | public void logUserEventPlay(String queryString, String streamingUrl) {
49 | logUserEvent(Event.PLAY, queryString, streamingUrl);
50 | }
51 |
52 | public void logUserEventStop(String queryString, String streamingUrl) {
53 | logUserEvent(Event.STOP, queryString, streamingUrl);
54 | }
55 |
56 | public void logUserEventPause(String queryString, String streamingUrl) {
57 | logUserEvent(Event.PAUSE, queryString, streamingUrl);
58 | }
59 |
60 | public void logUserEventSeek(String queryString, String streamingUrl) {
61 | logUserEvent(Event.SEEK, queryString, streamingUrl);
62 | }
63 |
64 | /**
65 | * Log the given event
66 | * @param event The event to log
67 | * @param queryString Query string to read parameters for
68 | * @param streamingURL The URL or this stream
69 | */
70 | private void logUserEvent(Event event, String queryString, String streamingURL) {
71 | if (queryString == null) {
72 | logger.warn("No logging was performed. Query string of client could not be found.");
73 | return;
74 | }
75 | try {
76 | Ticket streamingTicket = StringAndTextUtil.getTicket(queryString, ticketTool);
77 | String logString = new StreamingStatLogEntry(event, streamingTicket, streamingURL).getLogString();
78 | writeEventLog(logString);
79 | } catch (IllegallyFormattedQueryStringException e) {
80 | logger.warn("No logging was performed. Query string of client does not match expected format. Was "
81 | + queryString);
82 | }
83 | }
84 |
85 | protected synchronized void writeEventLog(String logString) {
86 | try {
87 | BufferedWriter statLogWriter = getStatLogWriter();
88 | statLogWriter.write(logString);
89 | statLogWriter.write(this.newlineString);
90 | statLogWriter.flush();
91 | } catch (IOException e) {
92 | logger.error("An IO-error occured when writing statistics log.", e);
93 | }
94 | }
95 |
96 | protected BufferedWriter getStatLogWriter() throws IOException {
97 | File currentStatLogFile;
98 | Date now = new Date();
99 | if ((statLogWriter == null) || (this.dateForNewLogFile.before(now))) {
100 | if (statLogWriter != null) {
101 | statLogWriter.close();
102 | }
103 | String filenameWithCorrectDate = getFilename(now);
104 | currentStatLogFile = new File(this.statLogFileHomeDir, filenameWithCorrectDate);
105 | this.logger.info("Creating log file: " + currentStatLogFile.getAbsolutePath());
106 | this.dateForNewLogFile = getFollowingMidnight(now);
107 | boolean newLogFile = !currentStatLogFile.exists();
108 | this.statLogWriter = Files.newBufferedWriter(currentStatLogFile.toPath(), StandardCharsets.UTF_8,
109 | StandardOpenOption.APPEND, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
110 | if (newLogFile) {
111 | this.statLogWriter.write(StreamingStatLogEntry.getLogStringHeadline());
112 | this.statLogWriter.write(this.newlineString);
113 | this.statLogWriter.flush();
114 | }
115 | }
116 | return statLogWriter;
117 | }
118 |
119 | protected Date getFollowingMidnight(Date date) {
120 | ZoneId zone = ZoneId.of("Europe/Copenhagen");
121 | LocalDate localDate = date.toInstant().atZone(zone).toLocalDate();
122 | LocalDateTime tomorroMidnight = LocalDateTime.of(localDate, LocalTime.MIDNIGHT).plusDays(1);
123 | return Date.from(tomorroMidnight.atZone(zone).toInstant());
124 | }
125 |
126 | protected void setDateForNewLogFile(Date dateForNewLogFile) {
127 | this.dateForNewLogFile = dateForNewLogFile;
128 | }
129 |
130 | public static String getFilename(Date time) {
131 | SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.ROOT);
132 | return FILENAME_PREFIX + sdf.format(time) + ".log";
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/wowza-ticket-checker-module/src/test/java/dk/statsbiblioteket/medieplatform/wowza/plugin/TicketCheckerTest.java:
--------------------------------------------------------------------------------
1 | package dk.statsbiblioteket.medieplatform.wowza.plugin;
2 |
3 | import com.wowza.wms.application.IApplicationInstance;
4 | import com.wowza.wms.client.IClient;
5 | import com.wowza.wms.logging.WMSLoggerFactory;
6 | import com.wowza.wms.stream.IMediaStream;
7 | import org.apache.log4j.Logger;
8 | import org.junit.jupiter.api.AfterEach;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.Test;
11 | import static org.junit.jupiter.api.Assertions.*;
12 | import org.mockito.invocation.InvocationOnMock;
13 |
14 | import dk.statsbiblioteket.medieplatform.ticketsystem.Property;
15 | import dk.statsbiblioteket.medieplatform.ticketsystem.Ticket;
16 | import dk.statsbiblioteket.medieplatform.wowza.plugin.ticket.TicketToolInterface;
17 |
18 | import static org.mockito.Mockito.mock;
19 | import static org.mockito.Mockito.when;
20 | import static org.mockito.ArgumentMatchers.*;
21 |
22 | import java.util.ArrayList;
23 | import java.util.Arrays;
24 | import java.util.HashMap;
25 | import java.util.List;
26 | import java.util.Map;
27 |
28 |
29 | public class TicketCheckerTest {
30 |
31 | public static final String QUERY_STRING = "ticket=";
32 | private Logger logger;
33 |
34 | TicketToolInterface ticketTool;
35 |
36 | IApplicationInstance iAppInstance = mock(IApplicationInstance.class);
37 |
38 | String goodIP = "127.0.0.1";
39 | String badIP = "127.0.0.2-Invalid-ip";
40 | String programID = "0ef8f946-4e90-4c9d-843a-a03504d2ee6c";
41 |
42 | String name = "0ef8f946-4e90-4c9d-843a-a03504d2ee6c.flv";
43 |
44 |
45 | public TicketCheckerTest() {
46 | super();
47 | this.logger = WMSLoggerFactory.getLogger(this.getClass());
48 | }
49 |
50 | @BeforeEach
51 | public void setUp() throws Exception {
52 | org.apache.log4j.BasicConfigurator.configure();
53 | ticketTool = mock(TicketToolInterface.class);
54 | }
55 |
56 | @AfterEach
57 | public void tearDown() throws Exception {
58 | org.apache.log4j.BasicConfigurator.resetConfiguration();
59 | }
60 |
61 | @Test
62 | public void testUserNotAllowedToPlayFile() {
63 | // Setup environment
64 | Map tickets = new HashMap<>();
65 | Ticket ticket = getTicket(badIP, programID);
66 | tickets.put(ticket.getId(), ticket);
67 | when(ticketTool.resolveTicket(anyString())).thenAnswer(
68 | (InvocationOnMock invocation) -> tickets.get((String) invocation.getArguments()[0]));
69 |
70 | String queryString = QUERY_STRING + ticket.getId();
71 |
72 | IClient iClient = mock(IClient.class);
73 | when(iClient.getQueryStr()).thenReturn(queryString);
74 | IMediaStream stream = mock(IMediaStream.class);
75 | when(stream.getClient()).thenReturn(iClient);
76 | when(stream.getQueryStr()).thenReturn(queryString);
77 | when(stream.getName()).thenReturn(name);
78 | TicketChecker ticketChecker = new TicketChecker("Stream", ticketTool);
79 | // Run test
80 | boolean result = ticketChecker.checkTicket(stream, stream.getClient());
81 | // Validate result
82 | assertFalse(result, "Expected not to be allowed");
83 | }
84 |
85 | @Test
86 | public void testNonExistingTicket() {
87 | // Setup environment
88 | String queryString = QUERY_STRING + "InvalidID";
89 |
90 | IClient iClient = mock(IClient.class);
91 | when(iClient.getQueryStr()).thenReturn(queryString);
92 | IMediaStream stream = mock(IMediaStream.class);
93 | when(stream.getClient()).thenReturn(iClient);
94 | when(stream.getQueryStr()).thenReturn(queryString);
95 | when(stream.getName()).thenReturn(name);
96 | TicketChecker ticketChecker = new TicketChecker("Stream", ticketTool);
97 | // Run test
98 | boolean result = ticketChecker.checkTicket(stream, stream.getClient());
99 | // Validate result
100 | assertFalse(result, "Expected not to be allowed");
101 | }
102 |
103 | @Test
104 | public void testGetFileToStreamSucces() {
105 | // Setup
106 | Map tickets = new HashMap<>();
107 | Ticket ticket = getTicket(goodIP, programID);
108 | tickets.put(ticket.getId(), ticket);
109 | when(ticketTool.resolveTicket(anyString())).thenAnswer(
110 | (InvocationOnMock invocation) -> tickets.get((String) invocation.getArguments()[0]));
111 |
112 | String queryString = QUERY_STRING + ticket.getId();
113 |
114 | IClient iClient = mock(IClient.class);
115 | when(iClient.getQueryStr()).thenReturn(queryString);
116 | when(iClient.getIp()).thenReturn(goodIP);
117 | IMediaStream stream = mock(IMediaStream.class);
118 | when(stream.getClient()).thenReturn(iClient);
119 | when(stream.getQueryStr()).thenReturn(queryString);
120 | when(stream.getName()).thenReturn(name);
121 | TicketChecker ticketChecker = new TicketChecker("Stream", ticketTool);
122 | // Test
123 | boolean result = ticketChecker.checkTicket(stream, stream.getClient());
124 | // Validate
125 | assertTrue(result, "Expected success");
126 | }
127 |
128 | @Test
129 | public void testWrongProgramId() {
130 | // Setup
131 | Map tickets = new HashMap<>();
132 | Ticket ticket = getTicket(goodIP, "anotherprogram");
133 | tickets.put(ticket.getId(), ticket);
134 | when(ticketTool.resolveTicket(anyString())).thenAnswer(
135 | (InvocationOnMock invocation) -> tickets.get((String) invocation.getArguments()[0]));
136 |
137 | String queryString = QUERY_STRING + ticket.getId();
138 |
139 | IClient iClient = mock(IClient.class);
140 | when(iClient.getQueryStr()).thenReturn(queryString);
141 | IMediaStream stream = mock(IMediaStream.class);
142 | when(stream.getClient()).thenReturn(iClient);
143 | when(stream.getQueryStr()).thenReturn(queryString);
144 | when(stream.getName()).thenReturn(name);
145 | TicketChecker ticketChecker = new TicketChecker("Stream", ticketTool);
146 | // Test
147 | boolean result = ticketChecker.checkTicket(stream, stream.getClient());
148 | // Validate
149 | assertFalse(result, "Expected not to be allowed");
150 | }
151 |
152 | private Ticket getTicket(String username, String resource) {
153 | Ticket ticket = new Ticket("Stream", username, Arrays.asList(resource), new HashMap>());
154 | return ticket;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------