Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions grails-app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//= require angular/angular.min
//= require angular/angular-route.min
//= require angular/angular-cookie.min
//= require angular/angular-resource.min
//= require angular/ng-grid-2.0.11/ng-grid.min
//= require angular/ui-bootstrap-tpls-0.6.0.min
//= require angular/ui-bootstrap-0.7.0.min
//= require_directory .

//= require_self

var meta = document.querySelector('meta[name="_csrf"]');
var csrfToken = meta ? meta.getAttribute('content') : '';
var csrfHeader = document.querySelector('meta[name="_csrf_header"]');
var csrfHeaderName = csrfHeader ? csrfHeader.getAttribute('content') : 'X-CSRF-TOKEN';

var app = angular.module('streama', [
'ngRoute', 'ngResource', 'ngGrid', 'ui.bootstrap', 'ngCookies'
]);

app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
request: function(config) {
if (csrfToken) {
config.headers[csrfHeaderName] = csrfToken;
}
return config;
}
};
});
}]);

app.factory('ApiService', ['$resource', function($resource) {
return {
shows: $resource('/api/show/:id', {id: '@id'}),
episodes: $resource('/api/episode/:id', {id: '@id'}),
movies: $resource('/api/movie/:id', {id: '@id'}),
files: $resource('/api/file/:id', {id: '@id'}),
genres: $resource('/api/genre/:id', {id: '@id'}),
tags: $resource('/api/tag/:id', {id: '@id'})
};
}]);

app.factory('SocketService', function() {
var sockets = {};
return {
getSocket: function(namespace) {
if (!sockets[namespace]) {
sockets[namespace] = io.connect(namespace);
}
return sockets[namespace];
}
};
});

app.controller('NavController', function($scope, $http, $location, ApiService) {
$scope.navItems = [
{label: 'Home', path: '/'},
{label: 'Shows', path: '/shows'},
{label: 'Movies', path: '/movies'},
{label: 'Upload', path: '/upload'},
{label: 'Admin', path: '/admin'}
];

$scope.isActive = function(path) {
return $location.path() === path;
};
});

app.controller('ShowController', function($scope, ApiService) {
$scope.shows = ApiService.shows.query();
});

app.controller('MovieController', function($scope, ApiService) {
$scope.movies = ApiService.movies.query();
});

app.controller('UploadController', function($scope, $http) {
$scope.uploadFile = function() {
var formData = new FormData();
formData.append('file', $scope.file);
$http.post('/api/upload', formData, {
headers: {'Content-Type': undefined}
}).then(function(response) {
$scope.message = 'Upload successful';
}).catch(function(error) {
$scope.error = 'Upload failed';
});
};
});

app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: '/assets/partials/home.html',
controller: 'HomeController'
})
.when('/shows', {
templateUrl: '/assets/partials/shows.html',
controller: 'ShowController'
})
.when('/movies', {
templateUrl: '/assets/partials/movies.html',
controller: 'MovieController'
})
.when('/upload', {
templateUrl: '/assets/partials/upload.html',
controller: 'UploadController'
})
.when('/admin', {
templateUrl: '/assets/partials/admin.html',
controller: 'AdminController'
})
.otherwise({
redirectTo: '/'
});
}]);

app.controller('HomeController', function($scope, ApiService) {
$scope.featuredShows = ApiService.shows.query({featured: true});
$scope.featuredMovies = ApiService.movies.query({featured: true});
});

app.controller('AdminController', function($scope, ApiService) {
$scope.shows = ApiService.shows.query();
$scope.movies = ApiService.movies.query();
$scope.genres = ApiService.genres.query();
});
6 changes: 6 additions & 0 deletions grails-app/conf/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ spring:
groovy:
template:
check-template-location: false
security:
csrf:
enabled: true
headerName: 'X-CSRF-TOKEN'
filter:
enabled: true

---
grails:
Expand Down
24 changes: 24 additions & 0 deletions grails-app/conf/spring/resources.groovy
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.csrf.CookieCsrfTokenRepository
import org.springframework.web.client.RestTemplate
import streama.LdapUserDetailsContextMapper

// Place your Spring DSL code here
beans = {

securityFilterChain(SecurityFilterChain) { bean ->
bean.factoryMethod = 'securityFilterChain'
bean.parent = ''
bean.autowireMode = 2
}

static securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers('/api/**', '/web-api/**')
.and()
.authorizeRequests()
.antMatchers('/assets/**', '/**/assets/**').permitAll()
.anyRequest().authenticated()
return http.build()
}
ldapUserDetailsMapper(LdapUserDetailsContextMapper) {
}

Expand Down
14 changes: 14 additions & 0 deletions grails-app/controllers/auth/CsrfController.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package auth

import grails.plugin.springweb.Security-conscious
import org.springframework.security.web.csrf.CsrfToken
import org.springframework.web.context.request.RequestContextHolder

@Security-conscious
class CsrfController {

def index() {
CsrfToken token = RequestContextHolder.requestAttributes.csrfToken
render([token: token?.token ?: ''] as JSON)
}
}
64 changes: 64 additions & 0 deletions grails-app/services/auth/CsrfTokenService.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package auth

import grails.core.GrailsApplication
import grails.util.Holders

class CsrfTokenService {

static final String TOKEN_ATTR = 'csrfToken'
static final int TOKEN_LENGTH = 32

GrailsApplication grailsApplication

String generateToken() {
def session = getSession()
if (!session) {
return null
}
String token = generateSecureToken()
session.setAttribute(TOKEN_ATTR, token)
return token
}

String getCurrentToken() {
def session = getSession()
if (!session) {
return null
}
return session.getAttribute(TOKEN_ATTR)
}

boolean validateToken(String token) {
if (!token) {
return false
}
def session = getSession()
if (!session) {
return false
}
String storedToken = session.getAttribute(TOKEN_ATTR)
return token == storedToken
}

void invalidateToken() {
def session = getSession()
if (session) {
session.removeAttribute(TOKEN_ATTR)
}
}

protected String generateSecureToken() {
SecureRandom random = new SecureRandom()
byte[] bytes = new byte[TOKEN_LENGTH]
random.nextBytes(bytes)
return bytes.encodeBase64Url().toString()
}

protected def getSession() {
try {
return Holders.getGrailsWebRequest()?.getSession()
} catch (Exception e) {
return null
}
}
}